瀏覽代碼

Sync with dev

sanex 4 年之前
父節點
當前提交
b463f774c8
共有 100 個文件被更改,包括 1191 次插入478 次删除
  1. 1 2
      .eslintrc.js
  2. 1 1
      .gitignore
  3. 0 1
      .npmignore
  4. 3 0
      .nycrc.json
  5. 16 9
      .travis.yml
  6. 27 0
      CHANGELOG.md
  7. 35 10
      README.md
  8. 0 0
      dist/index.browser.js
  9. 0 0
      dist/index.cli.js
  10. 0 0
      dist/index.js
  11. 38 37
      package.json
  12. 0 6
      scripts/build
  13. 0 3
      scripts/eslint
  14. 0 3
      scripts/git-add-files
  15. 0 3
      scripts/remove-cache-dir
  16. 0 3
      scripts/start
  17. 0 3
      scripts/test
  18. 0 4
      scripts/test-compile
  19. 0 7
      scripts/test-coveralls
  20. 0 3
      scripts/test-dev
  21. 0 3
      scripts/test-dev-compile-performance
  22. 0 3
      scripts/test-dev-runtime-performance
  23. 0 8
      scripts/test-full
  24. 0 6
      scripts/test-mocha
  25. 0 6
      scripts/test-mocha-memory-performance
  26. 0 3
      scripts/test-remove-tmp-dir
  27. 0 4
      scripts/travis
  28. 0 3
      scripts/watch
  29. 0 3
      scripts/webpack-dev
  30. 0 3
      scripts/webpack-prod
  31. 1 0
      src/JavaScriptObfuscator.ts
  32. 12 6
      src/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.ts
  33. 5 0
      src/cli/JavaScriptObfuscatorCLI.ts
  34. 33 12
      src/cli/utils/ObfuscatedCodeWriter.ts
  35. 1 1
      src/cli/utils/SourceCodeReader.ts
  36. 1 1
      src/constants/EcmaVersion.ts
  37. 1 1
      src/container/ServiceIdentifiers.ts
  38. 8 8
      src/container/modules/custom-nodes/CustomNodesModule.ts
  39. 10 0
      src/container/modules/node-transformers/FinalizingTransformersModule.ts
  40. 10 4
      src/container/modules/node-transformers/PreparingTransformersModule.ts
  41. 1 1
      src/container/modules/node-transformers/StringArrayTransformersModule.ts
  42. 17 1
      src/custom-code-helpers/string-array/StringArrayCodeHelper.ts
  43. 1 1
      src/custom-code-helpers/string-array/templates/string-array/StringArrayTemplate.ts
  44. 0 1
      src/custom-nodes/string-array-nodes/StringArrayCallNode.ts
  45. 1 0
      src/declarations/ESTree.d.ts
  46. 0 5
      src/declarations/js-string-escape.d.ts
  47. 1 1
      src/enums/custom-nodes/StringArrayCustomNode.ts
  48. 1 0
      src/enums/node-transformers/NodeTransformer.ts
  49. 1 0
      src/enums/node-transformers/preparing-transformers/obfuscating-guards/ObfuscatingGuard.ts
  50. 5 0
      src/enums/node/ObfuscatingGuardResult.ts
  51. 28 0
      src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts
  52. 2 2
      src/interfaces/node-transformers/preparing-transformers/obfuscating-guards/IObfuscatingGuard.ts
  53. 1 0
      src/interfaces/options/IOptions.ts
  54. 2 1
      src/node-transformers/control-flow-transformers/control-flow-replacers/StringLiteralControlFlowReplacer.ts
  55. 14 5
      src/node-transformers/converting-transformers/SplitStringTransformer.ts
  56. 2 1
      src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts
  57. 89 0
      src/node-transformers/finalizing-transformers/EscapeSequenceTransformer.ts
  58. 3 2
      src/node-transformers/preparing-transformers/EvalCallExpressionTransformer.ts
  59. 34 6
      src/node-transformers/preparing-transformers/ObfuscatingGuardsTransformer.ts
  60. 8 8
      src/node-transformers/preparing-transformers/obfuscating-guards/BlackListObfuscatingGuard.ts
  61. 13 11
      src/node-transformers/preparing-transformers/obfuscating-guards/ConditionalCommentObfuscatingGuard.ts
  62. 58 0
      src/node-transformers/preparing-transformers/obfuscating-guards/ForceTransformStringObfuscatingGuard.ts
  63. 9 5
      src/node-transformers/preparing-transformers/obfuscating-guards/ReservedStringObfuscatingGuard.ts
  64. 10 10
      src/node-transformers/string-array-transformers/StringArrayScopeCallsWrapperTransformer.ts
  65. 13 54
      src/node-transformers/string-array-transformers/StringArrayTransformer.ts
  66. 8 0
      src/node/NodeGuards.ts
  67. 8 0
      src/node/NodeLiteralUtils.ts
  68. 8 0
      src/node/NodeMetadata.ts
  69. 2 2
      src/node/NodeUtils.ts
  70. 10 0
      src/options/Options.ts
  71. 0 2
      src/options/OptionsNormalizer.ts
  72. 0 27
      src/options/normalizer-rules/StringArrayThresholdRule.ts
  73. 1 0
      src/options/presets/Default.ts
  74. 1 0
      src/options/presets/NoCustomNodes.ts
  75. 1 25
      src/storages/string-array-transformers/StringArrayStorage.ts
  76. 7 0
      src/types/container/custom-nodes/TStringArrayCustomNodeFactory.ts
  77. 0 7
      src/types/container/custom-nodes/TStringArrayTransformerCustomNodeFactory.ts
  78. 0 3
      src/types/node/TNodeGuard.ts
  79. 5 0
      src/types/node/TObfuscatingGuard.ts
  80. 11 0
      src/utils/StringUtils.ts
  81. 1 2
      test/declarations/index.d.ts
  82. 4 9
      test/dev/dev.ts
  83. 47 47
      test/functional-tests/cli/JavaScriptObfuscatorCLI.spec.ts
  84. 5 3
      test/functional-tests/code-transformers/preparing-transformers/hashbang-operator-transformer/HashbangOperatorTransformer.spec.ts
  85. 46 0
      test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  86. 4 0
      test/functional-tests/javascript-obfuscator/fixtures/eval-hello-world.js
  87. 80 8
      test/functional-tests/node-transformers/converting-transformers/split-string-transformer/SplitStringTransformer.spec.ts
  88. 51 0
      test/functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec.ts
  89. 25 0
      test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/for-await-expression.js
  90. 206 0
      test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/EscapeSequenceTransformer.spec.ts
  91. 0 0
      test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/error-when-non-latin.js
  92. 0 0
      test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/force-transform-strings-option.js
  93. 2 0
      test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/reserved-strings-option-1.js
  94. 0 0
      test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/reserved-strings-option-2.js
  95. 1 0
      test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/simple-input.js
  96. 9 0
      test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/wrappers-count.js
  97. 8 6
      test/functional-tests/node-transformers/initializing-transformers/comments-transformer/CommentsTransformer.spec.ts
  98. 69 0
      test/functional-tests/node-transformers/preparing-transformers/obfuscating-guards/force-transform-string-obfuscating-guard/ForceTransformStringObfuscatingGuard.spec.ts
  99. 2 0
      test/functional-tests/node-transformers/preparing-transformers/obfuscating-guards/force-transform-string-obfuscating-guard/fixtures/base-behaviour.js
  100. 52 52
      test/functional-tests/node-transformers/string-array-transformers/string-array-scope-calls-wrapper-transformer/StringArrayScopeCallsWrapperTransformer.spec.ts

+ 1 - 2
.eslintrc.js

@@ -205,7 +205,7 @@ module.exports = {
         "import/order": "off",
         "import/order": "off",
         "indent": "off",
         "indent": "off",
         "jsdoc/no-types": "off",
         "jsdoc/no-types": "off",
-        "linebreak-style": "error",
+        "linebreak-style": "off",
         "max-classes-per-file": [
         "max-classes-per-file": [
             "error",
             "error",
             1
             1
@@ -327,7 +327,6 @@ module.exports = {
                 "name": "error"
                 "name": "error"
             }
             }
         ],
         ],
-        "unicorn/no-array-instanceof": "error",
         "unicorn/no-nested-ternary": "error",
         "unicorn/no-nested-ternary": "error",
         "unicorn/no-unreadable-array-destructuring": "error",
         "unicorn/no-unreadable-array-destructuring": "error",
         "unicorn/prefer-includes": "error",
         "unicorn/prefer-includes": "error",

+ 1 - 1
.gitignore

@@ -2,12 +2,12 @@
 .DS_Store
 .DS_Store
 .idea
 .idea
 .nyc_output
 .nyc_output
+coverage
 npm-debug.log
 npm-debug.log
 *.js.map
 *.js.map
 /coverage
 /coverage
 /node_modules
 /node_modules
 /test/fixtures/compile-performance-obfuscated.js
 /test/fixtures/compile-performance-obfuscated.js
-/test-tmp
 /tmp
 /tmp
 /test/benchmark/**/**
 /test/benchmark/**/**
 *dockerfile
 *dockerfile

+ 0 - 1
.npmignore

@@ -7,4 +7,3 @@ examples
 images
 images
 webpack
 webpack
 test
 test
-test-tmp

+ 3 - 0
.nycrc.json

@@ -0,0 +1,3 @@
+{
+  "extends": "@istanbuljs/nyc-config-typescript"
+}

+ 16 - 9
.travis.yml

@@ -1,12 +1,20 @@
-sudo: false
 language: node_js
 language: node_js
 
 
-node_js:
-  - "10"
-  - "12"
-  - "13"
-  - "14"
-  - "stable"
+env:
+  - YARN_GPG=no
+
+jobs:
+  include:
+    - os: linux
+      node_js: "10"
+    - os: linux
+      node_js: "12"
+    - os: linux
+      node_js: "13"
+    - os: linux
+      node_js: "stable"
+    - os: windows
+      node_js: "14"
 
 
 cache:
 cache:
   yarn: true
   yarn: true
@@ -16,5 +24,4 @@ cache:
 script: "yarn run travis"
 script: "yarn run travis"
 
 
 after_success:
 after_success:
-  - yarn run test:coveralls
-  - rm -rf ./coverage
+  - yarn run test:mocha-coverage:report

+ 27 - 0
CHANGELOG.md

@@ -1,5 +1,32 @@
 Change Log
 Change Log
 
 
+v2.5.0
+---
+* Improved hierarchy of generated directories when `--output` is a directory path
+* Fixed wrong path generation for obfuscated files for `win32` environment. Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/576
+* Fixed wrong path generation under for source map for `win32` environment. Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/760
+* `javascript-obfuscator` now can be built under `win32` environment
+
+v2.4.3
+---
+* Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/769
+
+v2.4.2
+---
+* Fixed `URI-malformed` when `splitStrings` and `stringArrayEncoding` options are enabled. https://github.com/javascript-obfuscator/javascript-obfuscator/issues/530
+
+v2.4.1
+---
+* Small release with some README.md improvements that allow to use it on [obfuscator.io](https://obfuscator.io)
+
+v2.4.0
+---
+* **New option:** `forceTransformStrings` allows force transform strings even if by `stringArrayThreshold` (or possible other thresholds in the future) they shouldn't be transformed. Implemented https://github.com/javascript-obfuscator/javascript-obfuscator/issues/657
+
+v2.3.1
+---
+* Fixed a rare bug with `identifierNamesGenerator: 'mangled'` option that causes wrong identifier names generation
+
 v2.3.0
 v2.3.0
 ---
 ---
 * **New option:** `stringArrayWrappersType` allows to select a type of the wrappers that are appending by the `stringArrayWrappersCount` option
 * **New option:** `stringArrayWrappersType` allows to select a type of the wrappers that are appending by the `stringArrayWrappersCount` option

+ 35 - 10
README.md

@@ -35,7 +35,7 @@ The example of obfuscated code: [github.com](https://github.com/javascript-obfus
 [![npm version](https://badge.fury.io/js/javascript-obfuscator.svg)](https://badge.fury.io/js/javascript-obfuscator)
 [![npm version](https://badge.fury.io/js/javascript-obfuscator.svg)](https://badge.fury.io/js/javascript-obfuscator)
 [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fjavascript-obfuscator%2Fjavascript-obfuscator.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fjavascript-obfuscator%2Fjavascript-obfuscator?ref=badge_shield)
 [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fjavascript-obfuscator%2Fjavascript-obfuscator.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fjavascript-obfuscator%2Fjavascript-obfuscator?ref=badge_shield)
 [![Build Status](https://travis-ci.com/javascript-obfuscator/javascript-obfuscator.svg?branch=master)](https://travis-ci.com/javascript-obfuscator/javascript-obfuscator)
 [![Build Status](https://travis-ci.com/javascript-obfuscator/javascript-obfuscator.svg?branch=master)](https://travis-ci.com/javascript-obfuscator/javascript-obfuscator)
-[![Coverage Status](https://coveralls.io/repos/github/javascript-obfuscator/javascript-obfuscator/badge.svg?branch=master)](https://coveralls.io/github/javascript-obfuscator/javascript-obfuscator?branch=master)
+[![Coverage Status](https://coveralls.io/repos/github/javascript-obfuscator/javascript-obfuscator/badge.svg)](https://coveralls.io/github/javascript-obfuscator/javascript-obfuscator)
 [![Backers on Open Collective](https://opencollective.com/javascript-obfuscator/backers/badge.svg)](#backers) 
 [![Backers on Open Collective](https://opencollective.com/javascript-obfuscator/backers/badge.svg)](#backers) 
 [![Sponsors on Open Collective](https://opencollective.com/javascript-obfuscator/sponsors/badge.svg)](#sponsors)
 [![Sponsors on Open Collective](https://opencollective.com/javascript-obfuscator/sponsors/badge.svg)](#sponsors)
 [![xscode](https://img.shields.io/badge/Available%20on-xs%3Acode-blue?style=?style=plastic&logo=appveyor&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRF////////VXz1bAAAAAJ0Uk5T/wDltzBKAAAAlUlEQVR42uzXSwqAMAwE0Mn9L+3Ggtgkk35QwcnSJo9S+yGwM9DCooCbgn4YrJ4CIPUcQF7/XSBbx2TEz4sAZ2q1RAECBAiYBlCtvwN+KiYAlG7UDGj59MViT9hOwEqAhYCtAsUZvL6I6W8c2wcbd+LIWSCHSTeSAAECngN4xxIDSK9f4B9t377Wd7H5Nt7/Xz8eAgwAvesLRjYYPuUAAAAASUVORK5CYII=)](https://xscode.com/sanex3339/javascript-obfuscator)
 [![xscode](https://img.shields.io/badge/Available%20on-xs%3Acode-blue?style=?style=plastic&logo=appveyor&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRF////////VXz1bAAAAAJ0Uk5T/wDltzBKAAAAlUlEQVR42uzXSwqAMAwE0Mn9L+3Ggtgkk35QwcnSJo9S+yGwM9DCooCbgn4YrJ4CIPUcQF7/XSBbx2TEz4sAZ2q1RAECBAiYBlCtvwN+KiYAlG7UDGj59MViT9hOwEqAhYCtAsUZvL6I6W8c2wcbd+LIWSCHSTeSAAECngN4xxIDSK9f4B9t377Wd7H5Nt7/Xz8eAgwAvesLRjYYPuUAAAAASUVORK5CYII=)](https://xscode.com/sanex3339/javascript-obfuscator)
@@ -307,7 +307,7 @@ Kind of variables of inserted nodes will auto-detected, based on most prevailing
 ## Conflicts of identifier names between different files
 ## Conflicts of identifier names between different files
 
 
 During obfuscation of the different files, the same names can be generated for the global identifiers between these files.
 During obfuscation of the different files, the same names can be generated for the global identifiers between these files.
-To prevent this set the unique prefix for all global identifiers for each obfuscated file with [`identifiersPrefix`](#identifiersPrefix) option. 
+To prevent this set the unique prefix for all global identifiers for each obfuscated file with [`identifiersPrefix`](#identifiersprefix) option. 
 
 
 When using CLI this prefix will be added automatically.
 When using CLI this prefix will be added automatically.
 
 
@@ -339,6 +339,7 @@ Following options are available for the JS Obfuscator:
     debugProtectionInterval: false,
     debugProtectionInterval: false,
     disableConsoleOutput: false,
     disableConsoleOutput: false,
     domainLock: [],
     domainLock: [],
+    forceTransformStrings: [],
     identifierNamesGenerator: 'hexadecimal',
     identifierNamesGenerator: 'hexadecimal',
     identifiersDictionary: [],
     identifiersDictionary: [],
     identifiersPrefix: '',
     identifiersPrefix: '',
@@ -391,6 +392,7 @@ Following options are available for the JS Obfuscator:
     --disable-console-output <boolean>
     --disable-console-output <boolean>
     --domain-lock '<list>' (comma separated)
     --domain-lock '<list>' (comma separated)
     --exclude '<list>' (comma separated)
     --exclude '<list>' (comma separated)
+    --force-transform-strings '<list>' (comma separated)
     --identifier-names-generator <string> [dictionary, hexadecimal, mangled, mangled-shuffled]
     --identifier-names-generator <string> [dictionary, hexadecimal, mangled, mangled-shuffled]
     --identifiers-dictionary '<list>' (comma separated)
     --identifiers-dictionary '<list>' (comma separated)
     --identifiers-prefix <string>
     --identifiers-prefix <string>
@@ -423,6 +425,8 @@ Following options are available for the JS Obfuscator:
     --unicode-escape-sequence <boolean>
     --unicode-escape-sequence <boolean>
 ```
 ```
 
 
+<!-- ##options-start## -->
+
 ### `compact`
 ### `compact`
 Type: `boolean` Default: `true`
 Type: `boolean` Default: `true`
 
 
@@ -656,13 +660,32 @@ Type: `string[]` Default: `[]`
 
 
 A file names or globs which indicates files to exclude from obfuscation. 
 A file names or globs which indicates files to exclude from obfuscation. 
 
 
+### `forceTransformStrings`
+Type: `string[]` Default: `[]`
+
+Enables force transformation of string literals, which being matched by passed RegExp patterns.
+
+##### :warning: This option affects only strings that shouldn't be transformed by [`stringArrayThreshold`](#stringarraythreshold) (or possible other thresholds in the future)
+
+The option has a priority over `reservedStrings` option but hasn't a priority over `conditional comments`.
+
+Example:
+```ts
+	{
+		forceTransformStrings: [
+			'some-important-value',
+			'some-string_\d'
+		]
+	}
+```
+
 ### `identifierNamesGenerator`
 ### `identifierNamesGenerator`
 Type: `string` Default: `hexadecimal`
 Type: `string` Default: `hexadecimal`
 
 
 Sets identifier names generator.
 Sets identifier names generator.
 
 
 Available values:
 Available values:
-* `dictionary`: identifier names from [`identifiersDictionary`](#identifiersDictionary) list
+* `dictionary`: identifier names from [`identifiersDictionary`](#identifiersdictionary) list
 * `hexadecimal`: identifier names like `_0xabc123`
 * `hexadecimal`: identifier names like `_0xabc123`
 * `mangled`: short identifier names like `a`, `b`, `c`
 * `mangled`: short identifier names like `a`, `b`, `c`
 * `mangled-shuffled`: same as `mangled` but with shuffled alphabet
 * `mangled-shuffled`: same as `mangled` but with shuffled alphabet
@@ -670,7 +693,7 @@ Available values:
 ### `identifiersDictionary`
 ### `identifiersDictionary`
 Type: `string[]` Default: `[]`
 Type: `string[]` Default: `[]`
 
 
-Sets identifiers dictionary for [`identifierNamesGenerator`](#identifierNamesGenerator): `dictionary` option. Each identifier from the dictionary will be used in a few variants with a different casing of each character. Thus, the number of identifiers in the dictionary should depend on the identifiers amount at original source code.
+Sets identifiers dictionary for [`identifierNamesGenerator`](#identifiernamesgenerator): `dictionary` option. Each identifier from the dictionary will be used in a few variants with a different casing of each character. Thus, the number of identifiers in the dictionary should depend on the identifiers amount at original source code.
 
 
 ### `identifiersPrefix`
 ### `identifiersPrefix`
 Type: `string` Default: `''`
 Type: `string` Default: `''`
@@ -730,9 +753,9 @@ Type: `boolean` Default: `false`
 
 
 Enables renaming of property names. All built-in DOM properties and properties in core JavaScript classes will be ignored.
 Enables renaming of property names. All built-in DOM properties and properties in core JavaScript classes will be ignored.
 
 
-To set format of renamed property names use [`identifierNamesGenerator`](#identifierNamesGenerator) option.
+To set format of renamed property names use [`identifierNamesGenerator`](#identifiernamesgenerator) option.
 
 
-To control which properties will be renamed use [`reservedNames`](#reservedNames) option.
+To control which properties will be renamed use [`reservedNames`](#reservednames) option.
 
 
 Example: 
 Example: 
 ```ts
 ```ts
@@ -906,7 +929,7 @@ Specifies source map generation mode:
 ### `splitStrings`
 ### `splitStrings`
 Type: `boolean` Default: `false`
 Type: `boolean` Default: `false`
 
 
-Splits literal strings into chunks with length of [`splitStringsChunkLength`](#splitStringsChunkLength) option value.
+Splits literal strings into chunks with length of [`splitStringsChunkLength`](#splitstringschunklength) option value.
 
 
 Example:
 Example:
 ```ts
 ```ts
@@ -924,7 +947,7 @@ Example:
 ### `splitStringsChunkLength`
 ### `splitStringsChunkLength`
 Type: `number` Default: `10`
 Type: `number` Default: `10`
 
 
-Sets chunk length of [`splitStrings`](#splitStrings) option.
+Sets chunk length of [`splitStrings`](#splitstrings) option.
 
 
 ### `stringArray`
 ### `stringArray`
 Type: `boolean` Default: `true`
 Type: `boolean` Default: `true`
@@ -1012,7 +1035,7 @@ const eagle = _0x26ca42('0x5');
 ### `stringArrayWrappersChainedCalls`
 ### `stringArrayWrappersChainedCalls`
 Type: `boolean` Default: `true`
 Type: `boolean` Default: `true`
 
 
-##### :warning: [`stringArray`](#stringarray) and [`stringArrayWrappersCount`](#stringArrayWrappersCount) options must be enabled
+##### :warning: [`stringArray`](#stringarray) and [`stringArrayWrappersCount`](#stringarraywrapperscount) options must be enabled
 
 
 Enables the chained calls between `string array` wrappers.
 Enables the chained calls between `string array` wrappers.
 
 
@@ -1067,7 +1090,7 @@ function test() {
 ### `stringArrayWrappersType`
 ### `stringArrayWrappersType`
 Type: `string` Default: `variable`
 Type: `string` Default: `variable`
 
 
-##### :warning: [`stringArray`](#stringarray) and [`stringArrayWrappersCount`](#stringArrayWrappersCount) options must be enabled
+##### :warning: [`stringArray`](#stringarray) and [`stringArrayWrappersCount`](#stringarraywrapperscount) options must be enabled
 
 
 Allows to select a type of the wrappers that are appending by the `stringArrayWrappersCount` option.
 Allows to select a type of the wrappers that are appending by the `stringArrayWrappersCount` option.
 
 
@@ -1310,6 +1333,8 @@ Performance will slightly slower than without obfuscation
 }
 }
 ```
 ```
 
 
+<!-- ##options-end## -->
+
 ## Frequently Asked Questions
 ## Frequently Asked Questions
 
 
 ### What javascript versions are supported?
 ### What javascript versions are supported?

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


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


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


+ 38 - 37
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "javascript-obfuscator",
   "name": "javascript-obfuscator",
-  "version": "2.3.0",
+  "version": "2.5.0",
   "description": "JavaScript obfuscator",
   "description": "JavaScript obfuscator",
   "keywords": [
   "keywords": [
     "obfuscator",
     "obfuscator",
@@ -23,13 +23,13 @@
   "dependencies": {
   "dependencies": {
     "@gradecam/tsenum": "1.2.0",
     "@gradecam/tsenum": "1.2.0",
     "@nuxtjs/opencollective": "0.2.2",
     "@nuxtjs/opencollective": "0.2.2",
-    "acorn": "8.0.1",
+    "acorn": "8.0.3",
     "chalk": "4.1.0",
     "chalk": "4.1.0",
     "chance": "1.1.7",
     "chance": "1.1.7",
     "class-validator": "0.12.2",
     "class-validator": "0.12.2",
     "commander": "6.1.0",
     "commander": "6.1.0",
     "escodegen": "2.0.0",
     "escodegen": "2.0.0",
-    "eslint-scope": "^5.1.1",
+    "eslint-scope": "5.1.1",
     "estraverse": "5.2.0",
     "estraverse": "5.2.0",
     "eventemitter3": "4.0.7",
     "eventemitter3": "4.0.7",
     "fast-deep-equal": "3.1.3",
     "fast-deep-equal": "3.1.3",
@@ -41,35 +41,39 @@
     "reflect-metadata": "0.1.13",
     "reflect-metadata": "0.1.13",
     "source-map-support": "0.5.19",
     "source-map-support": "0.5.19",
     "string-template": "1.0.0",
     "string-template": "1.0.0",
-    "tslib": "2.0.1"
+    "stringz": "2.1.0",
+    "tslib": "2.0.3"
   },
   },
   "devDependencies": {
   "devDependencies": {
-    "@types/chai": "4.2.12",
+    "@istanbuljs/nyc-config-typescript": "1.0.1",
+    "@types/chai": "4.2.13",
     "@types/chance": "1.1.0",
     "@types/chance": "1.1.0",
     "@types/escodegen": "0.0.6",
     "@types/escodegen": "0.0.6",
     "@types/eslint-scope": "3.7.0",
     "@types/eslint-scope": "3.7.0",
     "@types/estraverse": "5.1.0",
     "@types/estraverse": "5.1.0",
     "@types/estree": "0.0.45",
     "@types/estree": "0.0.45",
+    "@types/js-string-escape": "1.0.0",
     "@types/md5": "2.2.0",
     "@types/md5": "2.2.0",
     "@types/mkdirp": "1.0.1",
     "@types/mkdirp": "1.0.1",
     "@types/mocha": "8.0.3",
     "@types/mocha": "8.0.3",
     "@types/multimatch": "4.0.0",
     "@types/multimatch": "4.0.0",
-    "@types/node": "14.11.1",
+    "@types/node": "14.11.8",
     "@types/rimraf": "3.0.0",
     "@types/rimraf": "3.0.0",
-    "@types/sinon": "9.0.5",
+    "@types/sinon": "9.0.8",
     "@types/string-template": "1.0.2",
     "@types/string-template": "1.0.2",
     "@types/webpack-env": "1.15.3",
     "@types/webpack-env": "1.15.3",
-    "@typescript-eslint/eslint-plugin": "4.1.1",
-    "@typescript-eslint/parser": "4.1.1",
+    "@typescript-eslint/eslint-plugin": "4.4.0",
+    "@typescript-eslint/parser": "4.4.0",
     "chai": "4.2.0",
     "chai": "4.2.0",
     "chai-exclude": "2.0.2",
     "chai-exclude": "2.0.2",
     "coveralls": "3.1.0",
     "coveralls": "3.1.0",
-    "eslint": "7.9.0",
-    "eslint-plugin-import": "2.22.0",
-    "eslint-plugin-jsdoc": "30.5.1",
+    "cross-env": "7.0.2",
+    "eslint": "7.11.0",
+    "eslint-plugin-import": "2.22.1",
+    "eslint-plugin-jsdoc": "30.6.4",
     "eslint-plugin-no-null": "1.0.2",
     "eslint-plugin-no-null": "1.0.2",
     "eslint-plugin-prefer-arrow": "1.2.2",
     "eslint-plugin-prefer-arrow": "1.2.2",
-    "eslint-plugin-unicorn": "21.0.0",
+    "eslint-plugin-unicorn": "22.0.0",
     "fork-ts-checker-notifier-webpack-plugin": "3.0.0",
     "fork-ts-checker-notifier-webpack-plugin": "3.0.0",
     "fork-ts-checker-webpack-plugin": "5.2.0",
     "fork-ts-checker-webpack-plugin": "5.2.0",
     "mocha": "8.1.3",
     "mocha": "8.1.3",
@@ -77,13 +81,13 @@
     "pjson": "1.0.9",
     "pjson": "1.0.9",
     "pre-commit": "1.2.2",
     "pre-commit": "1.2.2",
     "rimraf": "3.0.2",
     "rimraf": "3.0.2",
-    "sinon": "9.0.3",
+    "sinon": "9.2.0",
     "threads": "1.6.3",
     "threads": "1.6.3",
-    "ts-loader": "8.0.3",
+    "ts-loader": "8.0.4",
     "ts-node": "9.0.0",
     "ts-node": "9.0.0",
-    "typescript": "4.0.2",
-    "webpack": "5.0.0-beta.31",
-    "webpack-cli": "3.3.12",
+    "typescript": "4.1.0-beta",
+    "webpack": "5.0.0",
+    "webpack-cli": "4.0.0",
     "webpack-node-externals": "2.5.2"
     "webpack-node-externals": "2.5.2"
   },
   },
   "repository": {
   "repository": {
@@ -92,25 +96,22 @@
   },
   },
   "homepage": "https://obfuscator.io/",
   "homepage": "https://obfuscator.io/",
   "scripts": {
   "scripts": {
-    "start": "scripts/start",
-    "webpack:prod": "scripts/webpack-prod",
-    "webpack:dev": "scripts/webpack-dev",
-    "build": "scripts/build",
-    "watch": "scripts/watch",
-    "removeCacheDir": "scripts/remove-cache-dir",
-    "test:compile": "scripts/test-compile",
-    "test:dev": "scripts/test-dev",
-    "test:devCompilePerformance": "scripts/test-dev-compile-performance",
-    "test:devRuntimePerformance": "scripts/test-dev-runtime-performance",
-    "test:full": "scripts/test-full",
-    "test:coveralls": "scripts/test-coveralls",
-    "test:mocha": "scripts/test-mocha",
-    "test:mocha-memory-performance": "scripts/test-mocha-memory-performance",
-    "test:removeTmpDir": "scripts/test-remove-tmp-dir",
-    "test": "scripts/test",
-    "eslint": "scripts/eslint",
-    "travis": "scripts/travis",
-    "git:addFiles": "scripts/git-add-files",
+    "start": "yarn run watch",
+    "webpack:prod": "webpack --config ./webpack/webpack.node.config.js --config ./webpack/webpack.browser.config.js --mode production",
+    "build": "yarn run webpack:prod && yarn run eslint && yarn test",
+    "watch": "webpack --config ./webpack/webpack.node.config.js --mode development --watch",
+    "test:dev": "ts-node --type-check test/dev/dev.ts",
+    "test:devCompilePerformance": "ts-node test/dev/dev-compile-performance.ts",
+    "test:devRuntimePerformance": "ts-node test/dev/dev-runtime-performance.ts",
+    "test:full": "yarn run test:dev && yarn run test:mocha-coverage && yarn run test:mocha-memory-performance",
+    "test:mocha": "mocha --require ts-node/register --require source-map-support/register test/index.spec.ts --exit",
+    "test:mocha-coverage": "nyc --reporter text-summary --no-clean yarn run test:mocha",
+    "test:mocha-coverage:report": "nyc report --reporter=text-lcov | coveralls",
+    "test:mocha-memory-performance": "cross-env NODE_OPTIONS=--max-old-space-size=220 mocha --require ts-node/register test/performance-tests/JavaScriptObfuscatorMemory.spec.ts",
+    "test": "yarn run test:full",
+    "eslint": "eslint src/**/*.ts",
+    "travis": "yarn run eslint && yarn run test",
+    "git:addFiles": "git add .",
     "postinstall": "opencollective || exit 0"
     "postinstall": "opencollective || exit 0"
   },
   },
   "pre-commit": [
   "pre-commit": [

+ 0 - 6
scripts/build

@@ -1,6 +0,0 @@
-#!/bin/bash
-
-yarn run removeCacheDir &&
-yarn run webpack:prod &&
-yarn run eslint &&
-yarn test

+ 0 - 3
scripts/eslint

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-$(yarn bin)/eslint src/**/*.ts

+ 0 - 3
scripts/git-add-files

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-git add .

+ 0 - 3
scripts/remove-cache-dir

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-rm -rf .awcache

+ 0 - 3
scripts/start

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-yarn run watch

+ 0 - 3
scripts/test

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-yarn run test:full

+ 0 - 4
scripts/test-compile

@@ -1,4 +0,0 @@
-#!/bin/bash
-
-$(yarn bin)/tsc -p test/tsconfig.test.json --outDir test-tmp &&
-rsync -a --prune-empty-dirs --include '*/' --include '*.js' --include '*.json' --exclude '*' test/ test-tmp/test/

+ 0 - 7
scripts/test-coveralls

@@ -1,7 +0,0 @@
-#!/bin/bash
-
-yarn run test:removeTmpDir &&
-yarn run test:compile &&
-$(yarn bin)/nyc $(yarn bin)/mocha -- test-tmp/test/index.spec.js --exit &&
-$(yarn bin)/nyc report --reporter=text-lcov | $(yarn bin)/coveralls &&
-yarn run test:removeTmpDir

+ 0 - 3
scripts/test-dev

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-$(yarn bin)/ts-node --type-check test/dev/dev.ts

+ 0 - 3
scripts/test-dev-compile-performance

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-$(yarn bin)/ts-node test/dev/dev-compile-performance.ts

+ 0 - 3
scripts/test-dev-runtime-performance

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-$(yarn bin)/ts-node test/dev/dev-runtime-performance.ts

+ 0 - 8
scripts/test-full

@@ -1,8 +0,0 @@
-#!/bin/bash
-
-yarn run test:removeTmpDir &&
-yarn run test:compile &&
-yarn run test:dev &&
-$(yarn bin)/nyc --reporter text-summary $(yarn bin)/mocha -- test-tmp/test/index.spec.js --exit &&
-yarn run test:removeTmpDir &&
-yarn run test:mocha-memory-performance

+ 0 - 6
scripts/test-mocha

@@ -1,6 +0,0 @@
-#!/bin/bash
-
-yarn run test:removeTmpDir &&
-yarn run test:compile &&
-$(yarn bin)/mocha test-tmp/test/index.spec.js &&
-yarn run test:removeTmpDir

+ 0 - 6
scripts/test-mocha-memory-performance

@@ -1,6 +0,0 @@
-#!/bin/bash
-
-yarn run test:removeTmpDir &&
-yarn run test:compile &&
-NODE_OPTIONS=--max-old-space-size=200 $(yarn bin)/mocha test-tmp/test/performance-tests/JavaScriptObfuscatorMemory.spec.js &&
-yarn run test:removeTmpDir

+ 0 - 3
scripts/test-remove-tmp-dir

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-rm -rf test-tmp

+ 0 - 4
scripts/travis

@@ -1,4 +0,0 @@
-#!/bin/bash
-
-yarn run eslint &&
-yarn test

+ 0 - 3
scripts/watch

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-$(yarn bin)/webpack --config webpack/webpack.node.config.js --mode development --watch

+ 0 - 3
scripts/webpack-dev

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-$(yarn bin)/webpack --config webpack/webpack.node.config.js --mode production

+ 0 - 3
scripts/webpack-prod

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-$(yarn bin)/webpack --config webpack/webpack.node.config.js --config webpack/webpack.browser.config.js --mode production

+ 1 - 0
src/JavaScriptObfuscator.ts

@@ -68,6 +68,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         NodeTransformer.CommentsTransformer,
         NodeTransformer.CommentsTransformer,
         NodeTransformer.CustomCodeHelpersTransformer,
         NodeTransformer.CustomCodeHelpersTransformer,
         NodeTransformer.DeadCodeInjectionTransformer,
         NodeTransformer.DeadCodeInjectionTransformer,
+        NodeTransformer.EscapeSequenceTransformer,
         NodeTransformer.EvalCallExpressionTransformer,
         NodeTransformer.EvalCallExpressionTransformer,
         NodeTransformer.ExpressionStatementsMergeTransformer,
         NodeTransformer.ExpressionStatementsMergeTransformer,
         NodeTransformer.FunctionControlFlowTransformer,
         NodeTransformer.FunctionControlFlowTransformer,

+ 12 - 6
src/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.ts

@@ -11,8 +11,8 @@ import { IStringArrayStorageAnalyzer } from '../../interfaces/analyzers/string-a
 import { IStringArrayStorageItemData } from '../../interfaces/storages/string-array-transformers/IStringArrayStorageItem';
 import { IStringArrayStorageItemData } from '../../interfaces/storages/string-array-transformers/IStringArrayStorageItem';
 
 
 import { NodeGuards } from '../../node/NodeGuards';
 import { NodeGuards } from '../../node/NodeGuards';
-import { NodeMetadata } from '../../node/NodeMetadata';
 import { NodeLiteralUtils } from '../../node/NodeLiteralUtils';
 import { NodeLiteralUtils } from '../../node/NodeLiteralUtils';
+import { NodeMetadata } from '../../node/NodeMetadata';
 
 
 /**
 /**
  * Adds values of literal nodes to the string array storage
  * Adds values of literal nodes to the string array storage
@@ -99,7 +99,7 @@ export class StringArrayStorageAnalyzer implements IStringArrayStorageAnalyzer {
      * @param {Node} parentNode
      * @param {Node} parentNode
      */
      */
     private analyzeLiteralNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): void {
     private analyzeLiteralNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): void {
-        if (typeof literalNode.value !== 'string') {
+        if (!NodeLiteralUtils.isStringLiteralNode(literalNode)) {
             return;
             return;
         }
         }
 
 
@@ -107,7 +107,7 @@ export class StringArrayStorageAnalyzer implements IStringArrayStorageAnalyzer {
             return;
             return;
         }
         }
 
 
-        if (!this.shouldAddValueToStringArray(literalNode.value)) {
+        if (!this.shouldAddValueToStringArray(literalNode)) {
             return;
             return;
         }
         }
 
 
@@ -118,11 +118,17 @@ export class StringArrayStorageAnalyzer implements IStringArrayStorageAnalyzer {
     }
     }
 
 
     /**
     /**
-     * @param {string} value
+     * @param {(SimpleLiteral & {value: string})} literalNode
      * @returns {boolean}
      * @returns {boolean}
      */
      */
-    private shouldAddValueToStringArray (value: string): boolean {
-        return value.length >= StringArrayStorageAnalyzer.minimumLengthForStringArray
+    private shouldAddValueToStringArray (literalNode: ESTree.Literal & {value: string}): boolean {
+        const isForceTransformNode: boolean = NodeMetadata.isForceTransformNode(literalNode);
+
+        if (isForceTransformNode) {
+            return true;
+        }
+
+        return literalNode.value.length >= StringArrayStorageAnalyzer.minimumLengthForStringArray
             && this.randomGenerator.getMathRandom() <= this.options.stringArrayThreshold;
             && this.randomGenerator.getMathRandom() <= this.options.stringArrayThreshold;
     }
     }
 }
 }

+ 5 - 0
src/cli/JavaScriptObfuscatorCLI.ts

@@ -230,6 +230,11 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
                 'A filename or glob which indicates files to exclude from obfuscation',
                 'A filename or glob which indicates files to exclude from obfuscation',
                 ArraySanitizer
                 ArraySanitizer
             )
             )
+            .option(
+                '--force-transform-strings <list> (comma separated, without whitespaces)',
+                'Enables force transformation of string literals, which being matched by passed RegExp patterns (comma separated)',
+                ArraySanitizer
+            )
             .option(
             .option(
                 '--identifier-names-generator <string>',
                 '--identifier-names-generator <string>',
                 'Sets identifier names generator. ' +
                 'Sets identifier names generator. ' +

+ 33 - 12
src/cli/utils/ObfuscatedCodeWriter.ts

@@ -27,7 +27,7 @@ export class ObfuscatedCodeWriter {
         inputPath: string,
         inputPath: string,
         options: TInputCLIOptions
         options: TInputCLIOptions
     ) {
     ) {
-        this.inputPath = inputPath;
+        this.inputPath = path.normalize(inputPath);
         this.options = options;
         this.options = options;
     }
     }
 
 
@@ -36,13 +36,13 @@ export class ObfuscatedCodeWriter {
      * @returns {string}
      * @returns {string}
      */
      */
     public getOutputCodePath (filePath: string): string {
     public getOutputCodePath (filePath: string): string {
+        const normalizedFilePath: string = path.normalize(filePath);
         const normalizedRawOutputPath: string | null = this.options.output
         const normalizedRawOutputPath: string | null = this.options.output
             ? path.normalize(this.options.output)
             ? path.normalize(this.options.output)
             : null;
             : null;
 
 
         if (!normalizedRawOutputPath) {
         if (!normalizedRawOutputPath) {
-            return path
-                .normalize(filePath)
+            return normalizedFilePath
                 .split(StringSeparator.Dot)
                 .split(StringSeparator.Dot)
                 .map((value: string, index: number) => {
                 .map((value: string, index: number) => {
                     return index === 0 ? `${value}${JavaScriptObfuscatorCLI.obfuscatedFilePrefix}` : value;
                     return index === 0 ? `${value}${JavaScriptObfuscatorCLI.obfuscatedFilePrefix}` : value;
@@ -60,7 +60,9 @@ export class ObfuscatedCodeWriter {
 
 
         if (isDirectoryRawInputPath) {
         if (isDirectoryRawInputPath) {
             if (isDirectoryRawOutputPath) {
             if (isDirectoryRawOutputPath) {
-                return path.join(normalizedRawOutputPath, filePath);
+                const baseOutputPath: string = normalizedFilePath.replace(this.inputPath, '');
+
+                return path.join(normalizedRawOutputPath, baseOutputPath);
             } else {
             } else {
                 throw new Error('Output path for directory obfuscation should be a directory path');
                 throw new Error('Output path for directory obfuscation should be a directory path');
             }
             }
@@ -79,19 +81,38 @@ export class ObfuscatedCodeWriter {
      * @returns {string}
      * @returns {string}
      */
      */
     public getOutputSourceMapPath (outputCodePath: string, sourceMapFileName: string = ''): string {
     public getOutputSourceMapPath (outputCodePath: string, sourceMapFileName: string = ''): string {
+        if (!outputCodePath) {
+            throw new Error('Output code path is empty');
+        }
+
+        let normalizedOutputCodePath: string = path.normalize(outputCodePath);
+        let parsedOutputCodePath: path.ParsedPath = path.parse(normalizedOutputCodePath);
+
+        if (!parsedOutputCodePath.ext && !sourceMapFileName) {
+            throw new Error('Source map file name should be set when output code path is a directory path');
+        }
+
         if (sourceMapFileName) {
         if (sourceMapFileName) {
-            outputCodePath = `${outputCodePath.substring(
-                0, outputCodePath.lastIndexOf('/')
-            )}/${sourceMapFileName}`;
+            const indexOfLastSeparator: number = normalizedOutputCodePath.lastIndexOf(path.sep);
+            const sourceMapPath: string = parsedOutputCodePath.ext && indexOfLastSeparator > 0
+                ? normalizedOutputCodePath.slice(0, indexOfLastSeparator)
+                : normalizedOutputCodePath;
+            // remove possible drive letter for win32 environment
+            const normalizedSourceMapFilePath: string = sourceMapFileName.replace(/^[a-zA-Z]:\\*/, '');
+
+            normalizedOutputCodePath = path.join(sourceMapPath, normalizedSourceMapFilePath);
         }
         }
 
 
-        if (!/\.js\.map$/.test(outputCodePath)) {
-            outputCodePath = `${outputCodePath.split(StringSeparator.Dot)[0]}.js.map`;
-        } else if (/\.js$/.test(outputCodePath)) {
-            outputCodePath += '.map';
+        if (!/\.js\.map$/.test(normalizedOutputCodePath)) {
+            parsedOutputCodePath = path.parse(normalizedOutputCodePath);
+            const outputCodePathWithoutExtension: string = path.join(parsedOutputCodePath.dir, parsedOutputCodePath.name);
+
+            normalizedOutputCodePath = `${outputCodePathWithoutExtension}.js.map`;
+        } else if (/\.js$/.test(normalizedOutputCodePath)) {
+            normalizedOutputCodePath += '.map';
         }
         }
 
 
-        return outputCodePath;
+        return normalizedOutputCodePath;
     }
     }
 
 
     /**
     /**

+ 1 - 1
src/cli/utils/SourceCodeReader.ts

@@ -139,7 +139,7 @@ export class SourceCodeReader {
     private readDirectoryRecursive (directoryPath: string, filesData: IFileData[] = []): IFileData[] {
     private readDirectoryRecursive (directoryPath: string, filesData: IFileData[] = []): IFileData[] {
         fs.readdirSync(directoryPath, JavaScriptObfuscatorCLI.encoding)
         fs.readdirSync(directoryPath, JavaScriptObfuscatorCLI.encoding)
             .forEach((fileName: string) => {
             .forEach((fileName: string) => {
-                const filePath: string = `${directoryPath}/${fileName}`;
+                const filePath: string = path.join(directoryPath, fileName);
 
 
                 if (
                 if (
                     SourceCodeReader.isDirectoryPath(filePath)
                     SourceCodeReader.isDirectoryPath(filePath)

+ 1 - 1
src/constants/EcmaVersion.ts

@@ -1,3 +1,3 @@
 import * as acorn from 'acorn';
 import * as acorn from 'acorn';
 
 
-export const ecmaVersion: acorn.Options['ecmaVersion'] = 11;
+export const ecmaVersion: acorn.Options['ecmaVersion'] & number = 11;

+ 1 - 1
src/container/ServiceIdentifiers.ts

@@ -12,7 +12,7 @@ export enum ServiceIdentifiers {
     Factory__IObfuscatedCode = 'Factory<IObfuscatedCode>',
     Factory__IObfuscatedCode = 'Factory<IObfuscatedCode>',
     Factory__IObjectExpressionKeysTransformerCustomNode = 'Factory<IObjectExpressionKeysTransformerCustomNode>',
     Factory__IObjectExpressionKeysTransformerCustomNode = 'Factory<IObjectExpressionKeysTransformerCustomNode>',
     Factory__IObjectExpressionExtractor = 'Factory<IObjectExpressionExtractor>',
     Factory__IObjectExpressionExtractor = 'Factory<IObjectExpressionExtractor>',
-    Factory__IStringArrayTransformerCustomNode = 'Factory<IStringArrayTransformerCustomNode>',
+    Factory__IStringArrayCustomNode = 'Factory<IStringArrayCustomNode>',
     Factory__TControlFlowStorage = 'Factory<TControlFlowStorage>',
     Factory__TControlFlowStorage = 'Factory<TControlFlowStorage>',
     IArrayUtils = 'IArrayUtils',
     IArrayUtils = 'IArrayUtils',
     ICalleeDataExtractor = 'ICalleeDataExtractor',
     ICalleeDataExtractor = 'ICalleeDataExtractor',

+ 8 - 8
src/container/modules/custom-nodes/CustomNodesModule.ts

@@ -7,7 +7,7 @@ import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { ControlFlowCustomNode } from '../../../enums/custom-nodes/ControlFlowCustomNode';
 import { ControlFlowCustomNode } from '../../../enums/custom-nodes/ControlFlowCustomNode';
 import { DeadCodeInjectionCustomNode } from '../../../enums/custom-nodes/DeadCodeInjectionCustomNode';
 import { DeadCodeInjectionCustomNode } from '../../../enums/custom-nodes/DeadCodeInjectionCustomNode';
 import { ObjectExpressionKeysTransformerCustomNode } from '../../../enums/custom-nodes/ObjectExpressionKeysTransformerCustomNode';
 import { ObjectExpressionKeysTransformerCustomNode } from '../../../enums/custom-nodes/ObjectExpressionKeysTransformerCustomNode';
-import { StringArrayTransformerCustomNode } from '../../../enums/custom-nodes/StringArrayTransformerCustomNode';
+import { StringArrayCustomNode } from '../../../enums/custom-nodes/StringArrayCustomNode';
 
 
 import { ObjectExpressionVariableDeclarationHostNode } from '../../../custom-nodes/object-expression-keys-transformer-nodes/ObjectExpressionVariableDeclarationHostNode';
 import { ObjectExpressionVariableDeclarationHostNode } from '../../../custom-nodes/object-expression-keys-transformer-nodes/ObjectExpressionVariableDeclarationHostNode';
 import { BinaryExpressionFunctionNode } from '../../../custom-nodes/control-flow-flattening-nodes/BinaryExpressionFunctionNode';
 import { BinaryExpressionFunctionNode } from '../../../custom-nodes/control-flow-flattening-nodes/BinaryExpressionFunctionNode';
@@ -72,18 +72,18 @@ export const customNodesModule: interfaces.ContainerModule = new ContainerModule
         .toConstructor(ObjectExpressionVariableDeclarationHostNode)
         .toConstructor(ObjectExpressionVariableDeclarationHostNode)
         .whenTargetNamed(ObjectExpressionKeysTransformerCustomNode.ObjectExpressionVariableDeclarationHostNode);
         .whenTargetNamed(ObjectExpressionKeysTransformerCustomNode.ObjectExpressionVariableDeclarationHostNode);
 
 
-    // string array transformer nodes
+    // string array nodes
     bind<interfaces.Newable<ICustomNode>>(ServiceIdentifiers.Newable__ICustomNode)
     bind<interfaces.Newable<ICustomNode>>(ServiceIdentifiers.Newable__ICustomNode)
         .toConstructor(StringArrayCallNode)
         .toConstructor(StringArrayCallNode)
-        .whenTargetNamed(StringArrayTransformerCustomNode.StringArrayCallNode);
+        .whenTargetNamed(StringArrayCustomNode.StringArrayCallNode);
 
 
     bind<interfaces.Newable<ICustomNode>>(ServiceIdentifiers.Newable__ICustomNode)
     bind<interfaces.Newable<ICustomNode>>(ServiceIdentifiers.Newable__ICustomNode)
         .toConstructor(StringArrayScopeCallsWrapperFunctionNode)
         .toConstructor(StringArrayScopeCallsWrapperFunctionNode)
-        .whenTargetNamed(StringArrayTransformerCustomNode.StringArrayScopeCallsWrapperFunctionNode);
+        .whenTargetNamed(StringArrayCustomNode.StringArrayScopeCallsWrapperFunctionNode);
 
 
     bind<interfaces.Newable<ICustomNode>>(ServiceIdentifiers.Newable__ICustomNode)
     bind<interfaces.Newable<ICustomNode>>(ServiceIdentifiers.Newable__ICustomNode)
         .toConstructor(StringArrayScopeCallsWrapperVariableNode)
         .toConstructor(StringArrayScopeCallsWrapperVariableNode)
-        .whenTargetNamed(StringArrayTransformerCustomNode.StringArrayScopeCallsWrapperVariableNode);
+        .whenTargetNamed(StringArrayCustomNode.StringArrayScopeCallsWrapperVariableNode);
 
 
     // control flow customNode constructor factory
     // control flow customNode constructor factory
     bind<ICustomNode>(ServiceIdentifiers.Factory__IControlFlowCustomNode)
     bind<ICustomNode>(ServiceIdentifiers.Factory__IControlFlowCustomNode)
@@ -118,10 +118,10 @@ export const customNodesModule: interfaces.ContainerModule = new ContainerModule
                 ServiceIdentifiers.IOptions
                 ServiceIdentifiers.IOptions
             ));
             ));
 
 
-    // string array transformer customNode constructor factory
-    bind<ICustomNode>(ServiceIdentifiers.Factory__IStringArrayTransformerCustomNode)
+    // string array customNode constructor factory
+    bind<ICustomNode>(ServiceIdentifiers.Factory__IStringArrayCustomNode)
         .toFactory<ICustomNode>(InversifyContainerFacade
         .toFactory<ICustomNode>(InversifyContainerFacade
-            .getConstructorFactory<StringArrayTransformerCustomNode, ICustomNode>(
+            .getConstructorFactory<StringArrayCustomNode, ICustomNode>(
                 ServiceIdentifiers.Newable__ICustomNode,
                 ServiceIdentifiers.Newable__ICustomNode,
                 ServiceIdentifiers.Factory__IIdentifierNamesGenerator,
                 ServiceIdentifiers.Factory__IIdentifierNamesGenerator,
                 ServiceIdentifiers.ICustomCodeHelperFormatter,
                 ServiceIdentifiers.ICustomCodeHelperFormatter,

+ 10 - 0
src/container/modules/node-transformers/FinalizingTransformersModule.ts

@@ -1,5 +1,15 @@
 import { ContainerModule, interfaces } from 'inversify';
 import { ContainerModule, interfaces } from 'inversify';
+import { ServiceIdentifiers } from '../../ServiceIdentifiers';
+
+import { INodeTransformer } from '../../../interfaces/node-transformers/INodeTransformer';
+
+import { NodeTransformer } from '../../../enums/node-transformers/NodeTransformer';
+
+import { EscapeSequenceTransformer } from '../../../node-transformers/finalizing-transformers/EscapeSequenceTransformer';
 
 
 export const finalizingTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
 export const finalizingTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
     // finalizing transformers
     // finalizing transformers
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(EscapeSequenceTransformer)
+        .whenTargetNamed(NodeTransformer.EscapeSequenceTransformer);
 });
 });

+ 10 - 4
src/container/modules/node-transformers/PreparingTransformersModule.ts

@@ -12,6 +12,7 @@ import { BlackListObfuscatingGuard } from '../../../node-transformers/preparing-
 import { ConditionalCommentObfuscatingGuard } from '../../../node-transformers/preparing-transformers/obfuscating-guards/ConditionalCommentObfuscatingGuard';
 import { ConditionalCommentObfuscatingGuard } from '../../../node-transformers/preparing-transformers/obfuscating-guards/ConditionalCommentObfuscatingGuard';
 import { CustomCodeHelpersTransformer } from '../../../node-transformers/preparing-transformers/CustomCodeHelpersTransformer';
 import { CustomCodeHelpersTransformer } from '../../../node-transformers/preparing-transformers/CustomCodeHelpersTransformer';
 import { EvalCallExpressionTransformer } from '../../../node-transformers/preparing-transformers/EvalCallExpressionTransformer';
 import { EvalCallExpressionTransformer } from '../../../node-transformers/preparing-transformers/EvalCallExpressionTransformer';
+import { ForceTransformStringObfuscatingGuard } from '../../../node-transformers/preparing-transformers/obfuscating-guards/ForceTransformStringObfuscatingGuard';
 import { MetadataTransformer } from '../../../node-transformers/preparing-transformers/MetadataTransformer';
 import { MetadataTransformer } from '../../../node-transformers/preparing-transformers/MetadataTransformer';
 import { ObfuscatingGuardsTransformer } from '../../../node-transformers/preparing-transformers/ObfuscatingGuardsTransformer';
 import { ObfuscatingGuardsTransformer } from '../../../node-transformers/preparing-transformers/ObfuscatingGuardsTransformer';
 import { ParentificationTransformer } from '../../../node-transformers/preparing-transformers/ParentificationTransformer';
 import { ParentificationTransformer } from '../../../node-transformers/preparing-transformers/ParentificationTransformer';
@@ -40,6 +41,10 @@ export const preparingTransformersModule: interfaces.ContainerModule = new Conta
         .to(ParentificationTransformer)
         .to(ParentificationTransformer)
         .whenTargetNamed(NodeTransformer.ParentificationTransformer);
         .whenTargetNamed(NodeTransformer.ParentificationTransformer);
 
 
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(VariablePreserveTransformer)
+        .whenTargetNamed(NodeTransformer.VariablePreserveTransformer);
+
     // obfuscating guards
     // obfuscating guards
     bind<IObfuscatingGuard>(ServiceIdentifiers.INodeGuard)
     bind<IObfuscatingGuard>(ServiceIdentifiers.INodeGuard)
         .to(BlackListObfuscatingGuard)
         .to(BlackListObfuscatingGuard)
@@ -51,15 +56,16 @@ export const preparingTransformersModule: interfaces.ContainerModule = new Conta
         .inSingletonScope()
         .inSingletonScope()
         .whenTargetNamed(ObfuscatingGuard.ConditionalCommentObfuscatingGuard);
         .whenTargetNamed(ObfuscatingGuard.ConditionalCommentObfuscatingGuard);
 
 
+    bind<IObfuscatingGuard>(ServiceIdentifiers.INodeGuard)
+        .to(ForceTransformStringObfuscatingGuard)
+        .inSingletonScope()
+        .whenTargetNamed(ObfuscatingGuard.ForceTransformStringObfuscatingGuard);
+
     bind<IObfuscatingGuard>(ServiceIdentifiers.INodeGuard)
     bind<IObfuscatingGuard>(ServiceIdentifiers.INodeGuard)
         .to(ReservedStringObfuscatingGuard)
         .to(ReservedStringObfuscatingGuard)
         .inSingletonScope()
         .inSingletonScope()
         .whenTargetNamed(ObfuscatingGuard.ReservedStringObfuscatingGuard);
         .whenTargetNamed(ObfuscatingGuard.ReservedStringObfuscatingGuard);
 
 
-    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
-        .to(VariablePreserveTransformer)
-        .whenTargetNamed(NodeTransformer.VariablePreserveTransformer);
-
     // obfuscating guards factory
     // obfuscating guards factory
     bind<IObfuscatingGuard>(ServiceIdentifiers.Factory__INodeGuard)
     bind<IObfuscatingGuard>(ServiceIdentifiers.Factory__INodeGuard)
         .toFactory<IObfuscatingGuard>(InversifyContainerFacade
         .toFactory<IObfuscatingGuard>(InversifyContainerFacade

+ 1 - 1
src/container/modules/node-transformers/StringArrayTransformersModule.ts

@@ -9,7 +9,7 @@ import { StringArrayScopeCallsWrapperTransformer } from '../../../node-transform
 import { StringArrayTransformer } from '../../../node-transformers/string-array-transformers/StringArrayTransformer';
 import { StringArrayTransformer } from '../../../node-transformers/string-array-transformers/StringArrayTransformer';
 
 
 export const stringArrayTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
 export const stringArrayTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
-    // string array transformers
+    // strings transformers
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
         .to(StringArrayScopeCallsWrapperTransformer)
         .to(StringArrayScopeCallsWrapperTransformer)
         .whenTargetNamed(NodeTransformer.StringArrayScopeCallsWrapperTransformer);
         .whenTargetNamed(NodeTransformer.StringArrayScopeCallsWrapperTransformer);

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

@@ -9,6 +9,7 @@ import { ICustomCodeHelperObfuscator } from '../../interfaces/custom-code-helper
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IStringArrayStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayStorage';
 import { IStringArrayStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayStorage';
+import { IStringArrayStorageItemData } from '../../interfaces/storages/string-array-transformers/IStringArrayStorageItem';
 
 
 import { initializable } from '../../decorators/Initializable';
 import { initializable } from '../../decorators/Initializable';
 
 
@@ -16,6 +17,7 @@ import { StringArrayTemplate } from './templates/string-array/StringArrayTemplat
 
 
 import { AbstractCustomCodeHelper } from '../AbstractCustomCodeHelper';
 import { AbstractCustomCodeHelper } from '../AbstractCustomCodeHelper';
 import { NodeUtils } from '../../node/NodeUtils';
 import { NodeUtils } from '../../node/NodeUtils';
+import { StringUtils } from '../../utils/StringUtils';
 
 
 @injectable()
 @injectable()
 export class StringArrayCodeHelper extends AbstractCustomCodeHelper {
 export class StringArrayCodeHelper extends AbstractCustomCodeHelper {
@@ -81,7 +83,21 @@ export class StringArrayCodeHelper extends AbstractCustomCodeHelper {
     protected getCodeHelperTemplate (): string {
     protected getCodeHelperTemplate (): string {
         return this.customCodeHelperFormatter.formatTemplate(StringArrayTemplate(), {
         return this.customCodeHelperFormatter.formatTemplate(StringArrayTemplate(), {
             stringArrayName: this.stringArrayName,
             stringArrayName: this.stringArrayName,
-            stringArray: this.stringArrayStorage.toString()
+            stringArrayStorageItems: this.getEncodedStringArrayStorageItems()
         });
         });
     }
     }
+
+    /**
+     * @returns {string}
+     */
+    private getEncodedStringArrayStorageItems (): string {
+        return Array
+            .from(this.stringArrayStorage.getStorage().values())
+            .map((stringArrayStorageItemData: IStringArrayStorageItemData): string => {
+                const escapedEncodedValue: string = StringUtils.escapeJsString(stringArrayStorageItemData.encodedValue);
+
+                return `'${escapedEncodedValue}'`;
+            })
+            .toString();
+    }
 }
 }

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

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

+ 0 - 1
src/custom-nodes/string-array-nodes/StringArrayCallNode.ts

@@ -36,7 +36,6 @@ export class StringArrayCallNode extends AbstractStringArrayCallNode {
     @initializable()
     @initializable()
     private stringArrayCallsWrapperName!: string;
     private stringArrayCallsWrapperName!: string;
 
 
-
     /**
     /**
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
      * @param {ICustomCodeHelperFormatter} customCodeHelperFormatter
      * @param {ICustomCodeHelperFormatter} customCodeHelperFormatter

+ 1 - 0
src/declarations/ESTree.d.ts

@@ -6,6 +6,7 @@ import * as eslintScope from 'eslint-scope';
 
 
 declare module 'estree' {
 declare module 'estree' {
     export interface BaseNodeMetadata {
     export interface BaseNodeMetadata {
+        forceTransformNode?: boolean;
         ignoredNode?: boolean;
         ignoredNode?: boolean;
     }
     }
 
 

+ 0 - 5
src/declarations/js-string-escape.d.ts

@@ -1,5 +0,0 @@
-declare module 'js-string-escape' {
-    function jsStringEscape (input: string): string;
-
-    export = jsStringEscape;
-}

+ 1 - 1
src/enums/custom-nodes/StringArrayTransformerCustomNode.ts → src/enums/custom-nodes/StringArrayCustomNode.ts

@@ -1,4 +1,4 @@
-export enum StringArrayTransformerCustomNode {
+export enum StringArrayCustomNode {
     StringArrayCallNode = 'StringArrayCallNode',
     StringArrayCallNode = 'StringArrayCallNode',
     StringArrayScopeCallsWrapperFunctionNode = 'StringArrayScopeCallsWrapperFunctionNode',
     StringArrayScopeCallsWrapperFunctionNode = 'StringArrayScopeCallsWrapperFunctionNode',
     StringArrayScopeCallsWrapperVariableNode = 'StringArrayScopeCallsWrapperVariableNode'
     StringArrayScopeCallsWrapperVariableNode = 'StringArrayScopeCallsWrapperVariableNode'

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

@@ -5,6 +5,7 @@ export enum NodeTransformer {
     CommentsTransformer = 'CommentsTransformer',
     CommentsTransformer = 'CommentsTransformer',
     CustomCodeHelpersTransformer = 'CustomCodeHelpersTransformer',
     CustomCodeHelpersTransformer = 'CustomCodeHelpersTransformer',
     DeadCodeInjectionTransformer = 'DeadCodeInjectionTransformer',
     DeadCodeInjectionTransformer = 'DeadCodeInjectionTransformer',
+    EscapeSequenceTransformer = 'EscapeSequenceTransformer',
     EvalCallExpressionTransformer = 'EvalCallExpressionTransformer',
     EvalCallExpressionTransformer = 'EvalCallExpressionTransformer',
     ExpressionStatementsMergeTransformer = 'ExpressionStatementsMergeTransformer',
     ExpressionStatementsMergeTransformer = 'ExpressionStatementsMergeTransformer',
     FunctionControlFlowTransformer = 'FunctionControlFlowTransformer',
     FunctionControlFlowTransformer = 'FunctionControlFlowTransformer',

+ 1 - 0
src/enums/node-transformers/preparing-transformers/obfuscating-guards/ObfuscatingGuard.ts

@@ -1,5 +1,6 @@
 export enum ObfuscatingGuard {
 export enum ObfuscatingGuard {
     BlackListObfuscatingGuard = 'BlackListObfuscatingGuard',
     BlackListObfuscatingGuard = 'BlackListObfuscatingGuard',
     ConditionalCommentObfuscatingGuard = 'ConditionalCommentObfuscatingGuard',
     ConditionalCommentObfuscatingGuard = 'ConditionalCommentObfuscatingGuard',
+    ForceTransformStringObfuscatingGuard = 'ForceTransformStringObfuscatingGuard',
     ReservedStringObfuscatingGuard = 'ReservedStringObfuscatingGuard'
     ReservedStringObfuscatingGuard = 'ReservedStringObfuscatingGuard'
 }
 }

+ 5 - 0
src/enums/node/ObfuscatingGuardResult.ts

@@ -0,0 +1,5 @@
+export enum ObfuscatingGuardResult {
+    ForceTransform = 'ForceTransform',
+    Ignore = 'Ignore',
+    Transform = 'Transform'
+}

+ 28 - 0
src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts

@@ -64,6 +64,7 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
      * @param {string} prevName
      * @param {string} prevName
      * @returns {boolean}
      * @returns {boolean}
      */
      */
+    // eslint-disable-next-line complexity
     public static isIncrementedMangledName (nextName: string, prevName: string): boolean {
     public static isIncrementedMangledName (nextName: string, prevName: string): boolean {
         if (nextName === prevName) {
         if (nextName === prevName) {
             return false;
             return false;
@@ -76,6 +77,8 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
             return nextNameLength > prevNameLength;
             return nextNameLength > prevNameLength;
         }
         }
 
 
+        let isIncrementedPrevCharacter: boolean = false;
+
         for (let i: number = 0; i < nextNameLength; i++) {
         for (let i: number = 0; i < nextNameLength; i++) {
             const nextNameCharacter: string = nextName[i];
             const nextNameCharacter: string = nextName[i];
             const prevNameCharacter: string = prevName[i];
             const prevNameCharacter: string = prevName[i];
@@ -84,6 +87,17 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
                 continue;
                 continue;
             }
             }
 
 
+            const isDigitNextNameCharacter: boolean = MangledIdentifierNamesGenerator.isDigitCharacter(nextNameCharacter);
+            const isDigitPrevNameCharacter: boolean = MangledIdentifierNamesGenerator.isDigitCharacter(prevNameCharacter);
+
+            if (
+                isIncrementedPrevCharacter
+                && isDigitNextNameCharacter
+                && !isDigitPrevNameCharacter
+            ) {
+                return true;
+            }
+
             const isUpperCaseNextNameCharacter: boolean = MangledIdentifierNamesGenerator.isUpperCaseCharacter(nextNameCharacter);
             const isUpperCaseNextNameCharacter: boolean = MangledIdentifierNamesGenerator.isUpperCaseCharacter(nextNameCharacter);
             const isUpperCasePrevNameCharacter: boolean = MangledIdentifierNamesGenerator.isUpperCaseCharacter(prevNameCharacter);
             const isUpperCasePrevNameCharacter: boolean = MangledIdentifierNamesGenerator.isUpperCaseCharacter(prevNameCharacter);
 
 
@@ -98,6 +112,12 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
             ) {
             ) {
                 return false;
                 return false;
             }
             }
+
+            isIncrementedPrevCharacter = nextNameCharacter > prevNameCharacter;
+
+            if (nextNameCharacter < prevNameCharacter) {
+                return false;
+            }
         }
         }
 
 
         return nextName > prevName;
         return nextName > prevName;
@@ -111,6 +131,14 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
         return /^[A-Z]*$/.test(string);
         return /^[A-Z]*$/.test(string);
     }
     }
 
 
+    /**
+     * @param {string} character
+     * @returns {boolean}
+     */
+    private static isDigitCharacter (string: string): boolean {
+        return /^[0-9]*$/.test(string);
+    }
+
     /**
     /**
      * Generates next name based on a global previous mangled name
      * Generates next name based on a global previous mangled name
      * We can ignore nameLength parameter here, it hasn't sense with this generator
      * We can ignore nameLength parameter here, it hasn't sense with this generator

+ 2 - 2
src/interfaces/node-transformers/preparing-transformers/obfuscating-guards/IObfuscatingGuard.ts

@@ -1,9 +1,9 @@
-import { TNodeGuard } from '../../../../types/node/TNodeGuard';
+import { TObfuscatingGuard } from '../../../../types/node/TObfuscatingGuard';
 
 
 export interface IObfuscatingGuard {
 export interface IObfuscatingGuard {
     /**
     /**
      * @param {Node} node
      * @param {Node} node
      * @returns {boolean}
      * @returns {boolean}
      */
      */
-    check: TNodeGuard;
+    check: TObfuscatingGuard;
 }
 }

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

@@ -18,6 +18,7 @@ export interface IOptions {
     readonly debugProtectionInterval: boolean;
     readonly debugProtectionInterval: boolean;
     readonly disableConsoleOutput: boolean;
     readonly disableConsoleOutput: boolean;
     readonly domainLock: string[];
     readonly domainLock: string[];
+    readonly forceTransformStrings: string[];
     readonly identifierNamesGenerator: TypeFromEnum<typeof IdentifierNamesGenerator>;
     readonly identifierNamesGenerator: TypeFromEnum<typeof IdentifierNamesGenerator>;
     readonly identifiersDictionary: string[];
     readonly identifiersDictionary: string[];
     readonly identifiersPrefix: string;
     readonly identifiersPrefix: string;

+ 2 - 1
src/node-transformers/control-flow-transformers/control-flow-replacers/StringLiteralControlFlowReplacer.ts

@@ -16,6 +16,7 @@ import { ControlFlowCustomNode } from '../../../enums/custom-nodes/ControlFlowCu
 
 
 import { AbstractControlFlowReplacer } from './AbstractControlFlowReplacer';
 import { AbstractControlFlowReplacer } from './AbstractControlFlowReplacer';
 import { NodeGuards } from '../../../node/NodeGuards';
 import { NodeGuards } from '../../../node/NodeGuards';
+import { NodeLiteralUtils } from '../../../node/NodeLiteralUtils';
 import { StringLiteralControlFlowStorageCallNode } from '../../../custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/StringLiteralControlFlowStorageCallNode';
 import { StringLiteralControlFlowStorageCallNode } from '../../../custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/StringLiteralControlFlowStorageCallNode';
 import { StringLiteralNode } from '../../../custom-nodes/control-flow-flattening-nodes/StringLiteralNode';
 import { StringLiteralNode } from '../../../custom-nodes/control-flow-flattening-nodes/StringLiteralNode';
 
 
@@ -55,7 +56,7 @@ export class StringLiteralControlFlowReplacer extends AbstractControlFlowReplace
             return literalNode;
             return literalNode;
         }
         }
 
 
-        if (typeof literalNode.value !== 'string' || literalNode.value.length < 3) {
+        if (!NodeLiteralUtils.isStringLiteralNode(literalNode) || literalNode.value.length < 3) {
             return literalNode;
             return literalNode;
         }
         }
 
 

+ 14 - 5
src/node-transformers/converting-transformers/SplitStringTransformer.ts

@@ -3,6 +3,7 @@ import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
 import * as estraverse from 'estraverse';
 import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 import * as ESTree from 'estree';
+import * as stringz from 'stringz';
 
 
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
@@ -48,11 +49,16 @@ export class SplitStringTransformer extends AbstractNodeTransformer {
 
 
     /**
     /**
      * @param {string} string
      * @param {string} string
+     * @param {number} stringLength
      * @param {number} chunkSize
      * @param {number} chunkSize
      * @returns {string[]}
      * @returns {string[]}
      */
      */
-    private static chunkString (string: string, chunkSize: number): string[] {
-        const chunksCount: number = Math.ceil(string.length / chunkSize);
+    private static chunkString (
+        string: string,
+        stringLength: number,
+        chunkSize: number
+    ): string[] {
+        const chunksCount: number = Math.ceil(stringLength / chunkSize);
         const chunks: string[] = [];
         const chunks: string[] = [];
 
 
         let nextChunkStartIndex: number = 0;
         let nextChunkStartIndex: number = 0;
@@ -62,7 +68,7 @@ export class SplitStringTransformer extends AbstractNodeTransformer {
             chunkIndex < chunksCount;
             chunkIndex < chunksCount;
             ++chunkIndex, nextChunkStartIndex += chunkSize
             ++chunkIndex, nextChunkStartIndex += chunkSize
         ) {
         ) {
-            chunks[chunkIndex] = string.substr(nextChunkStartIndex, chunkSize);
+            chunks[chunkIndex] = stringz.substr(string, nextChunkStartIndex, chunkSize);
         }
         }
 
 
         return chunks;
         return chunks;
@@ -140,16 +146,19 @@ export class SplitStringTransformer extends AbstractNodeTransformer {
         parentNode: ESTree.Node,
         parentNode: ESTree.Node,
         chunkLength: number
         chunkLength: number
     ): ESTree.Node {
     ): ESTree.Node {
-        if (typeof literalNode.value !== 'string') {
+        if (!NodeLiteralUtils.isStringLiteralNode(literalNode)) {
             return literalNode;
             return literalNode;
         }
         }
 
 
-        if (chunkLength >= literalNode.value.length) {
+        const valueLength: number = stringz.length(literalNode.value);
+
+        if (chunkLength >= valueLength) {
             return literalNode;
             return literalNode;
         }
         }
 
 
         const stringChunks: string[] = SplitStringTransformer.chunkString(
         const stringChunks: string[] = SplitStringTransformer.chunkString(
             literalNode.value,
             literalNode.value,
+            valueLength,
             chunkLength
             chunkLength
         );
         );
 
 

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

@@ -106,7 +106,8 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
             || NodeGuards.isContinueStatementNode(targetNode)
             || NodeGuards.isContinueStatementNode(targetNode)
             || NodeGuards.isAwaitExpressionNode(targetNode)
             || NodeGuards.isAwaitExpressionNode(targetNode)
             || NodeGuards.isYieldExpressionNode(targetNode)
             || NodeGuards.isYieldExpressionNode(targetNode)
-            || NodeGuards.isSuperNode(targetNode);
+            || NodeGuards.isSuperNode(targetNode)
+            || (NodeGuards.isForOfStatementNode(targetNode) && targetNode.await);
     }
     }
 
 
     /**
     /**

+ 89 - 0
src/node-transformers/finalizing-transformers/EscapeSequenceTransformer.ts

@@ -0,0 +1,89 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+
+import { IEscapeSequenceEncoder } from '../../interfaces/utils/IEscapeSequenceEncoder';
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
+
+import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
+import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { NodeGuards } from '../../node/NodeGuards';
+import { NodeLiteralUtils } from '../../node/NodeLiteralUtils';
+import { NodeFactory } from '../../node/NodeFactory';
+import { NodeUtils } from '../../node/NodeUtils';
+
+@injectable()
+export class EscapeSequenceTransformer extends AbstractNodeTransformer {
+    /**
+     * @type {NodeTransformer[]}
+     */
+    public readonly runAfter: NodeTransformer[] = [
+        NodeTransformer.CustomCodeHelpersTransformer
+    ];
+
+    /**
+     * @type {IEscapeSequenceEncoder}
+     */
+    private readonly escapeSequenceEncoder: IEscapeSequenceEncoder;
+
+    /**
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     * @param {IEscapeSequenceEncoder} escapeSequenceEncoder
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions,
+        @inject(ServiceIdentifiers.IEscapeSequenceEncoder) escapeSequenceEncoder: IEscapeSequenceEncoder
+    ) {
+        super(randomGenerator, options);
+
+        this.escapeSequenceEncoder = escapeSequenceEncoder;
+    }
+
+    /**
+     * @param {NodeTransformationStage} nodeTransformationStage
+     * @returns {IVisitor | null}
+     */
+    public getVisitor (nodeTransformationStage: NodeTransformationStage): IVisitor | null {
+        switch (nodeTransformationStage) {
+            case NodeTransformationStage.Finalizing:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
+                        if (NodeGuards.isLiteralNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * @param {Literal} literalNode
+     * @param {Node | null} parentNode
+     * @returns {Literal}
+     */
+    public transformNode (literalNode: ESTree.Literal, parentNode: ESTree.Node | null): ESTree.Literal {
+        if (!NodeLiteralUtils.isStringLiteralNode(literalNode)) {
+            return literalNode;
+        }
+
+        const encodedValue: string = this.escapeSequenceEncoder.encode(
+            literalNode.value,
+            this.options.unicodeEscapeSequence
+        );
+        const newLiteralNode: ESTree.Literal = NodeFactory.literalNode(encodedValue);
+
+        NodeUtils.parentizeNode(newLiteralNode, parentNode);
+
+        return newLiteralNode;
+    }
+}

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

@@ -2,7 +2,6 @@ import { inject, injectable, } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
 import * as ESTree from 'estree';
 import * as ESTree from 'estree';
-import jsStringEscape from 'js-string-escape';
 
 
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
@@ -15,6 +14,7 @@ import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeFactory } from '../../node/NodeFactory';
 import { NodeFactory } from '../../node/NodeFactory';
 import { NodeGuards } from '../../node/NodeGuards';
 import { NodeGuards } from '../../node/NodeGuards';
 import { NodeUtils } from '../../node/NodeUtils';
 import { NodeUtils } from '../../node/NodeUtils';
+import { StringUtils } from '../../utils/StringUtils';
 
 
 @injectable()
 @injectable()
 export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
 export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
@@ -22,6 +22,7 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
      * @type {NodeTransformer.ParentificationTransformer[]}
      * @type {NodeTransformer.ParentificationTransformer[]}
      */
      */
     public readonly runAfter: NodeTransformer[] = [
     public readonly runAfter: NodeTransformer[] = [
+        NodeTransformer.EscapeSequenceTransformer,
         NodeTransformer.ParentificationTransformer,
         NodeTransformer.ParentificationTransformer,
         NodeTransformer.VariablePreserveTransformer
         NodeTransformer.VariablePreserveTransformer
     ];
     ];
@@ -179,7 +180,7 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
         return NodeFactory.callExpressionNode(
         return NodeFactory.callExpressionNode(
             NodeFactory.identifierNode('eval'),
             NodeFactory.identifierNode('eval'),
             [
             [
-                NodeFactory.literalNode(jsStringEscape(obfuscatedCode))
+                NodeFactory.literalNode(StringUtils.escapeJsString(obfuscatedCode))
             ]
             ]
         );
         );
     }
     }

+ 34 - 6
src/node-transformers/preparing-transformers/ObfuscatingGuardsTransformer.ts

@@ -11,8 +11,9 @@ import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 
 import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
 import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
-import { ObfuscatingGuard } from '../../enums/node-transformers/preparing-transformers/obfuscating-guards/ObfuscatingGuard';
 import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
 import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
+import { ObfuscatingGuard } from '../../enums/node-transformers/preparing-transformers/obfuscating-guards/ObfuscatingGuard';
+import { ObfuscatingGuardResult } from '../../enums/node/ObfuscatingGuardResult';
 
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
 import { NodeGuards } from '../../node/NodeGuards';
@@ -29,6 +30,7 @@ export class ObfuscatingGuardsTransformer extends AbstractNodeTransformer {
     private static readonly obfuscatingGuardsList: ObfuscatingGuard[] = [
     private static readonly obfuscatingGuardsList: ObfuscatingGuard[] = [
         ObfuscatingGuard.BlackListObfuscatingGuard,
         ObfuscatingGuard.BlackListObfuscatingGuard,
         ObfuscatingGuard.ConditionalCommentObfuscatingGuard,
         ObfuscatingGuard.ConditionalCommentObfuscatingGuard,
+        ObfuscatingGuard.ForceTransformStringObfuscatingGuard,
         ObfuscatingGuard.ReservedStringObfuscatingGuard
         ObfuscatingGuard.ReservedStringObfuscatingGuard
     ];
     ];
 
 
@@ -84,13 +86,39 @@ export class ObfuscatingGuardsTransformer extends AbstractNodeTransformer {
      * @returns {Node}
      * @returns {Node}
      */
      */
     public transformNode (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node {
     public transformNode (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node {
-        const obfuscationAllowed: boolean = this.obfuscatingGuards
-            .every((nodeGuard: IObfuscatingGuard) => nodeGuard.check(node));
+        const obfuscatingGuardResults: ObfuscatingGuardResult[] = this.obfuscatingGuards
+            .map((obfuscatingGuard: IObfuscatingGuard) => obfuscatingGuard.check(node));
 
 
-        NodeMetadata.set(node, {
-            ignoredNode: !(NodeGuards.isProgramNode(node) || obfuscationAllowed)
-        });
+        this.setNodeMetadata(node, obfuscatingGuardResults);
 
 
         return node;
         return node;
     }
     }
+
+    /**
+     * @param {Node} node
+     * @param {ObfuscatingGuardResult[]} obfuscatingGuardResults
+     */
+    private setNodeMetadata (node: ESTree.Node, obfuscatingGuardResults: ObfuscatingGuardResult[]): void {
+        const isTransformNode: boolean = obfuscatingGuardResults
+            .every((obfuscatingGuardResult: ObfuscatingGuardResult) => obfuscatingGuardResult === ObfuscatingGuardResult.Transform);
+
+        let isForceTransformNode: boolean = false;
+        let isIgnoredNode: boolean = false;
+
+        if (!isTransformNode) {
+            isForceTransformNode = obfuscatingGuardResults
+                .some((obfuscatingGuardResult: ObfuscatingGuardResult) =>
+                    obfuscatingGuardResult === ObfuscatingGuardResult.ForceTransform
+                );
+            isIgnoredNode = !isForceTransformNode && obfuscatingGuardResults
+                .some((obfuscatingGuardResult: ObfuscatingGuardResult) =>
+                    obfuscatingGuardResult === ObfuscatingGuardResult.Ignore
+                );
+        }
+
+        NodeMetadata.set(node, {
+            forceTransformNode: isForceTransformNode && !NodeGuards.isProgramNode(node),
+            ignoredNode: isIgnoredNode && !NodeGuards.isProgramNode(node)
+        });
+    }
 }
 }

+ 8 - 8
src/node-transformers/preparing-transformers/obfuscating-guards/BlackListObfuscatingGuard.ts

@@ -2,10 +2,10 @@ import { injectable } from 'inversify';
 
 
 import * as ESTree from 'estree';
 import * as ESTree from 'estree';
 
 
-import { TNodeGuard } from '../../../types/node/TNodeGuard';
-
 import { IObfuscatingGuard } from '../../../interfaces/node-transformers/preparing-transformers/obfuscating-guards/IObfuscatingGuard';
 import { IObfuscatingGuard } from '../../../interfaces/node-transformers/preparing-transformers/obfuscating-guards/IObfuscatingGuard';
 
 
+import { ObfuscatingGuardResult } from '../../../enums/node/ObfuscatingGuardResult';
+
 import { NodeGuards } from '../../../node/NodeGuards';
 import { NodeGuards } from '../../../node/NodeGuards';
 
 
 @injectable()
 @injectable()
@@ -13,7 +13,7 @@ export class BlackListObfuscatingGuard implements IObfuscatingGuard {
     /**
     /**
      * @type {((node: Node) => boolean)[]}
      * @type {((node: Node) => boolean)[]}
      */
      */
-    private static readonly blackListGuards: TNodeGuard[] = [
+    private static readonly blackListGuards: ((node: ESTree.Node) => boolean)[] = [
         NodeGuards.isUseStrictOperator
         NodeGuards.isUseStrictOperator
     ];
     ];
 
 
@@ -27,16 +27,16 @@ export class BlackListObfuscatingGuard implements IObfuscatingGuard {
     }
     }
 
 
     /**
     /**
-     * @returns {boolean}
-     * @param node
+     * @param {Node} node
+     * @returns {ObfuscatingGuardResult}
      */
      */
-    public check (node: ESTree.Node): boolean {
+    public check (node: ESTree.Node): ObfuscatingGuardResult {
         for (let i: number = 0; i < this.blackListGuardsLength; i++) {
         for (let i: number = 0; i < this.blackListGuardsLength; i++) {
             if (BlackListObfuscatingGuard.blackListGuards[i](node)) {
             if (BlackListObfuscatingGuard.blackListGuards[i](node)) {
-                return false;
+                return ObfuscatingGuardResult.Ignore;
             }
             }
         }
         }
 
 
-        return true;
+        return ObfuscatingGuardResult.Transform;
     }
     }
 }
 }

+ 13 - 11
src/node-transformers/preparing-transformers/obfuscating-guards/ConditionalCommentObfuscatingGuard.ts

@@ -4,6 +4,8 @@ import * as ESTree from 'estree';
 
 
 import { IObfuscatingGuard } from '../../../interfaces/node-transformers/preparing-transformers/obfuscating-guards/IObfuscatingGuard';
 import { IObfuscatingGuard } from '../../../interfaces/node-transformers/preparing-transformers/obfuscating-guards/IObfuscatingGuard';
 
 
+import { ObfuscatingGuardResult } from '../../../enums/node/ObfuscatingGuardResult';
+
 import { NodeGuards } from '../../../node/NodeGuards';
 import { NodeGuards } from '../../../node/NodeGuards';
 
 
 @injectable()
 @injectable()
@@ -33,21 +35,21 @@ export class ConditionalCommentObfuscatingGuard implements IObfuscatingGuard {
     }
     }
 
 
     /**
     /**
-     * @returns {boolean}
-     * @param node
+     * @param {Node} node
+     * @returns {ObfuscatingGuardResult}
      */
      */
-    public check (node: ESTree.Node): boolean {
-        if (!NodeGuards.isNodeWithComments(node)) {
-            return this.obfuscationAllowed;
-        }
+    public check (node: ESTree.Node): ObfuscatingGuardResult {
+        if (NodeGuards.isNodeWithComments(node)) {
+            const leadingComments: ESTree.Comment[] | undefined = node.leadingComments;
 
 
-        const leadingComments: ESTree.Comment[] | undefined = node.leadingComments;
-
-        if (leadingComments) {
-            this.obfuscationAllowed = this.checkComments(leadingComments);
+            if (leadingComments) {
+                this.obfuscationAllowed = this.checkComments(leadingComments);
+            }
         }
         }
 
 
-        return this.obfuscationAllowed;
+        return this.obfuscationAllowed
+            ? ObfuscatingGuardResult.Transform
+            : ObfuscatingGuardResult.Ignore;
     }
     }
 
 
     /**
     /**

+ 58 - 0
src/node-transformers/preparing-transformers/obfuscating-guards/ForceTransformStringObfuscatingGuard.ts

@@ -0,0 +1,58 @@
+import { inject, injectable } from 'inversify';
+
+import * as ESTree from 'estree';
+
+import { IObfuscatingGuard } from '../../../interfaces/node-transformers/preparing-transformers/obfuscating-guards/IObfuscatingGuard';
+import { IOptions } from '../../../interfaces/options/IOptions';
+
+import { ObfuscatingGuardResult } from '../../../enums/node/ObfuscatingGuardResult';
+
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import { NodeGuards } from '../../../node/NodeGuards';
+
+@injectable()
+export class ForceTransformStringObfuscatingGuard implements IObfuscatingGuard {
+    /**
+     * @type {IOptions}
+     */
+    private readonly options: IOptions;
+
+    /**
+     * @param {IOptions} options
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        this.options = options;
+    }
+
+    /**
+     * @param {Node} node
+     * @returns {ObfuscatingGuardResult}
+     */
+    public check (node: ESTree.Node): ObfuscatingGuardResult {
+        if (
+            this.options.forceTransformStrings.length
+            && NodeGuards.isLiteralNode(node)
+            && typeof node.value === 'string'
+        ) {
+            return !this.isForceTransformString(node.value)
+                ? ObfuscatingGuardResult.Transform
+                : ObfuscatingGuardResult.ForceTransform;
+        }
+
+        return ObfuscatingGuardResult.Transform;
+    }
+
+    /**
+     * @param {string} value
+     * @returns {boolean}
+     */
+    private isForceTransformString (value: string): boolean {
+        return this.options.forceTransformStrings
+            .some((forceTransformString: string) => {
+                return new RegExp(forceTransformString, 'g').exec(value) !== null;
+            });
+    }
+}

+ 9 - 5
src/node-transformers/preparing-transformers/obfuscating-guards/ReservedStringObfuscatingGuard.ts

@@ -5,6 +5,8 @@ import * as ESTree from 'estree';
 import { IObfuscatingGuard } from '../../../interfaces/node-transformers/preparing-transformers/obfuscating-guards/IObfuscatingGuard';
 import { IObfuscatingGuard } from '../../../interfaces/node-transformers/preparing-transformers/obfuscating-guards/IObfuscatingGuard';
 import { IOptions } from '../../../interfaces/options/IOptions';
 import { IOptions } from '../../../interfaces/options/IOptions';
 
 
+import { ObfuscatingGuardResult } from '../../../enums/node/ObfuscatingGuardResult';
+
 import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 
 
 import { NodeGuards } from '../../../node/NodeGuards';
 import { NodeGuards } from '../../../node/NodeGuards';
@@ -26,19 +28,21 @@ export class ReservedStringObfuscatingGuard implements IObfuscatingGuard {
     }
     }
 
 
     /**
     /**
-     * @returns {boolean}
-     * @param node
+     * @param {Node} node
+     * @returns {ObfuscatingGuardResult}
      */
      */
-    public check (node: ESTree.Node): boolean {
+    public check (node: ESTree.Node): ObfuscatingGuardResult {
         if (
         if (
             this.options.reservedStrings.length
             this.options.reservedStrings.length
             && NodeGuards.isLiteralNode(node)
             && NodeGuards.isLiteralNode(node)
             && typeof node.value === 'string'
             && typeof node.value === 'string'
         ) {
         ) {
-            return !this.isReservedString(node.value);
+            return !this.isReservedString(node.value)
+                ? ObfuscatingGuardResult.Transform
+                : ObfuscatingGuardResult.Ignore;
         }
         }
 
 
-        return true;
+        return ObfuscatingGuardResult.Transform;
     }
     }
 
 
     /**
     /**

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

@@ -7,7 +7,7 @@ import { TInitialData } from '../../types/TInitialData';
 import { TNodeWithLexicalScopeStatements } from '../../types/node/TNodeWithLexicalScopeStatements';
 import { TNodeWithLexicalScopeStatements } from '../../types/node/TNodeWithLexicalScopeStatements';
 import { TStatement } from '../../types/node/TStatement';
 import { TStatement } from '../../types/node/TStatement';
 import { TStringArrayScopeCallsWrapperNamesDataByEncoding } from '../../types/node-transformers/string-array-transformers/TStringArrayScopeCallsWrapperNamesDataByEncoding';
 import { TStringArrayScopeCallsWrapperNamesDataByEncoding } from '../../types/node-transformers/string-array-transformers/TStringArrayScopeCallsWrapperNamesDataByEncoding';
-import { TStringArrayTransformerCustomNodeFactory } from '../../types/container/custom-nodes/TStringArrayTransformerCustomNodeFactory';
+import { TStringArrayCustomNodeFactory } from '../../types/container/custom-nodes/TStringArrayCustomNodeFactory';
 
 
 import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
 import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IOptions } from '../../interfaces/options/IOptions';
@@ -21,14 +21,14 @@ import { IVisitedLexicalScopeNodesStackStorage } from '../../interfaces/storages
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 
 import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
 import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
-import { StringArrayTransformerCustomNode } from '../../enums/custom-nodes/StringArrayTransformerCustomNode';
+import { StringArrayCustomNode } from '../../enums/custom-nodes/StringArrayCustomNode';
 import { StringArrayWrappersType } from '../../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 import { StringArrayWrappersType } from '../../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeAppender } from '../../node/NodeAppender';
 import { NodeAppender } from '../../node/NodeAppender';
 import { NodeGuards } from '../../node/NodeGuards';
 import { NodeGuards } from '../../node/NodeGuards';
-import { StringArrayScopeCallsWrapperVariableNode } from '../../custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperVariableNode';
 import { StringArrayScopeCallsWrapperFunctionNode } from '../../custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperFunctionNode';
 import { StringArrayScopeCallsWrapperFunctionNode } from '../../custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperFunctionNode';
+import { StringArrayScopeCallsWrapperVariableNode } from '../../custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperVariableNode';
 
 
 @injectable()
 @injectable()
 export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransformer {
 export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransformer {
@@ -48,9 +48,9 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
     private readonly stringArrayScopeCallsWrapperNamesDataStorage: IStringArrayScopeCallsWrapperNamesDataStorage;
     private readonly stringArrayScopeCallsWrapperNamesDataStorage: IStringArrayScopeCallsWrapperNamesDataStorage;
 
 
     /**
     /**
-     * @type {TStringArrayTransformerCustomNodeFactory}
+     * @type {TStringArrayCustomNodeFactory}
      */
      */
-    private readonly stringArrayTransformerCustomNodeFactory: TStringArrayTransformerCustomNodeFactory;
+    private readonly stringArrayTransformerCustomNodeFactory: TStringArrayCustomNodeFactory;
 
 
     /**
     /**
      * @type {IVisitedLexicalScopeNodesStackStorage}
      * @type {IVisitedLexicalScopeNodesStackStorage}
@@ -64,7 +64,7 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
      * @param {IStringArrayStorage} stringArrayStorage
      * @param {IStringArrayStorage} stringArrayStorage
      * @param {IStringArrayScopeCallsWrapperNamesDataStorage} stringArrayScopeCallsWrapperNamesDataStorage
      * @param {IStringArrayScopeCallsWrapperNamesDataStorage} stringArrayScopeCallsWrapperNamesDataStorage
      * @param {IStringArrayScopeCallsWrapperLexicalScopeDataStorage} stringArrayScopeCallsWrapperLexicalScopeDataStorage
      * @param {IStringArrayScopeCallsWrapperLexicalScopeDataStorage} stringArrayScopeCallsWrapperLexicalScopeDataStorage
-     * @param {TStringArrayTransformerCustomNodeFactory} stringArrayTransformerCustomNodeFactory
+     * @param {TStringArrayCustomNodeFactory} stringArrayTransformerCustomNodeFactory
      */
      */
     public constructor (
     public constructor (
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
@@ -73,8 +73,8 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
         @inject(ServiceIdentifiers.IStringArrayStorage) stringArrayStorage: IStringArrayStorage,
         @inject(ServiceIdentifiers.IStringArrayStorage) stringArrayStorage: IStringArrayStorage,
         @inject(ServiceIdentifiers.IStringArrayScopeCallsWrapperNamesDataStorage) stringArrayScopeCallsWrapperNamesDataStorage: IStringArrayScopeCallsWrapperNamesDataStorage,
         @inject(ServiceIdentifiers.IStringArrayScopeCallsWrapperNamesDataStorage) stringArrayScopeCallsWrapperNamesDataStorage: IStringArrayScopeCallsWrapperNamesDataStorage,
         @inject(ServiceIdentifiers.IStringArrayScopeCallsWrapperLexicalScopeDataStorage) stringArrayScopeCallsWrapperLexicalScopeDataStorage: IStringArrayScopeCallsWrapperLexicalScopeDataStorage,
         @inject(ServiceIdentifiers.IStringArrayScopeCallsWrapperLexicalScopeDataStorage) stringArrayScopeCallsWrapperLexicalScopeDataStorage: IStringArrayScopeCallsWrapperLexicalScopeDataStorage,
-        @inject(ServiceIdentifiers.Factory__IStringArrayTransformerCustomNode)
-            stringArrayTransformerCustomNodeFactory: TStringArrayTransformerCustomNodeFactory
+        @inject(ServiceIdentifiers.Factory__IStringArrayCustomNode)
+            stringArrayTransformerCustomNodeFactory: TStringArrayCustomNodeFactory
     ) {
     ) {
         super(randomGenerator, options);
         super(randomGenerator, options);
 
 
@@ -278,7 +278,7 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
     ): TStatement[] {
     ): TStatement[] {
         const stringArrayScopeCallsWrapperVariableNode: ICustomNode<TInitialData<StringArrayScopeCallsWrapperVariableNode>> =
         const stringArrayScopeCallsWrapperVariableNode: ICustomNode<TInitialData<StringArrayScopeCallsWrapperVariableNode>> =
             this.stringArrayTransformerCustomNodeFactory(
             this.stringArrayTransformerCustomNodeFactory(
-                StringArrayTransformerCustomNode.StringArrayScopeCallsWrapperVariableNode
+                StringArrayCustomNode.StringArrayScopeCallsWrapperVariableNode
             );
             );
 
 
         stringArrayScopeCallsWrapperVariableNode.initialize(
         stringArrayScopeCallsWrapperVariableNode.initialize(
@@ -302,7 +302,7 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
     ): TStatement[] {
     ): TStatement[] {
         const stringArrayScopeCallsWrapperFunctionNode: ICustomNode<TInitialData<StringArrayScopeCallsWrapperFunctionNode>> =
         const stringArrayScopeCallsWrapperFunctionNode: ICustomNode<TInitialData<StringArrayScopeCallsWrapperFunctionNode>> =
             this.stringArrayTransformerCustomNodeFactory(
             this.stringArrayTransformerCustomNodeFactory(
-                StringArrayTransformerCustomNode.StringArrayScopeCallsWrapperFunctionNode
+                StringArrayCustomNode.StringArrayScopeCallsWrapperFunctionNode
             );
             );
 
 
         stringArrayScopeCallsWrapperFunctionNode.initialize(
         stringArrayScopeCallsWrapperFunctionNode.initialize(

+ 13 - 54
src/node-transformers/string-array-transformers/StringArrayTransformer.ts

@@ -8,10 +8,9 @@ import { TInitialData } from '../../types/TInitialData';
 import { TNodeWithLexicalScopeStatements } from '../../types/node/TNodeWithLexicalScopeStatements';
 import { TNodeWithLexicalScopeStatements } from '../../types/node/TNodeWithLexicalScopeStatements';
 import { TStatement } from '../../types/node/TStatement';
 import { TStatement } from '../../types/node/TStatement';
 import { TStringArrayScopeCallsWrapperNamesDataByEncoding } from '../../types/node-transformers/string-array-transformers/TStringArrayScopeCallsWrapperNamesDataByEncoding';
 import { TStringArrayScopeCallsWrapperNamesDataByEncoding } from '../../types/node-transformers/string-array-transformers/TStringArrayScopeCallsWrapperNamesDataByEncoding';
-import { TStringArrayTransformerCustomNodeFactory } from '../../types/container/custom-nodes/TStringArrayTransformerCustomNodeFactory';
+import { TStringArrayCustomNodeFactory } from '../../types/container/custom-nodes/TStringArrayCustomNodeFactory';
 
 
 import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
 import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
-import { IEscapeSequenceEncoder } from '../../interfaces/utils/IEscapeSequenceEncoder';
 import { IIdentifierNamesGenerator } from '../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
 import { IIdentifierNamesGenerator } from '../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
 import { ILiteralNodesCacheStorage } from '../../interfaces/storages/string-array-transformers/ILiteralNodesCacheStorage';
 import { ILiteralNodesCacheStorage } from '../../interfaces/storages/string-array-transformers/ILiteralNodesCacheStorage';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IOptions } from '../../interfaces/options/IOptions';
@@ -26,11 +25,10 @@ import { IVisitedLexicalScopeNodesStackStorage } from '../../interfaces/storages
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 
 import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
 import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
-import { StringArrayTransformerCustomNode } from '../../enums/custom-nodes/StringArrayTransformerCustomNode';
+import { StringArrayCustomNode } from '../../enums/custom-nodes/StringArrayCustomNode';
 import { StringArrayWrappersType } from '../../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 import { StringArrayWrappersType } from '../../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
-import { NodeFactory } from '../../node/NodeFactory';
 import { NodeGuards } from '../../node/NodeGuards';
 import { NodeGuards } from '../../node/NodeGuards';
 import { NodeLiteralUtils } from '../../node/NodeLiteralUtils';
 import { NodeLiteralUtils } from '../../node/NodeLiteralUtils';
 import { NodeMetadata } from '../../node/NodeMetadata';
 import { NodeMetadata } from '../../node/NodeMetadata';
@@ -49,10 +47,6 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
      */
      */
     private static readonly maxShiftedIndexValue: number = 1000;
     private static readonly maxShiftedIndexValue: number = 1000;
 
 
-    /**
-     * @type {IEscapeSequenceEncoder}
-     */
-    private readonly escapeSequenceEncoder: IEscapeSequenceEncoder;
 
 
     /**
     /**
      * @type {IIdentifierNamesGenerator}
      * @type {IIdentifierNamesGenerator}
@@ -85,9 +79,9 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
     private readonly stringArrayScopeCallsWrapperNamesDataStorage: IStringArrayScopeCallsWrapperNamesDataStorage;
     private readonly stringArrayScopeCallsWrapperNamesDataStorage: IStringArrayScopeCallsWrapperNamesDataStorage;
 
 
     /**
     /**
-     * @type {TStringArrayTransformerCustomNodeFactory}
+     * @type {TStringArrayCustomNodeFactory}
      */
      */
-    private readonly stringArrayTransformerCustomNodeFactory: TStringArrayTransformerCustomNodeFactory;
+    private readonly stringArrayTransformerCustomNodeFactory: TStringArrayCustomNodeFactory;
 
 
     /**
     /**
      * @type {IVisitedLexicalScopeNodesStackStorage}
      * @type {IVisitedLexicalScopeNodesStackStorage}
@@ -97,7 +91,6 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
     /**
     /**
      * @param {IRandomGenerator} randomGenerator
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
      * @param {IOptions} options
-     * @param {IEscapeSequenceEncoder} escapeSequenceEncoder
      * @param {ILiteralNodesCacheStorage} literalNodesCacheStorage
      * @param {ILiteralNodesCacheStorage} literalNodesCacheStorage
      * @param {IVisitedLexicalScopeNodesStackStorage} visitedLexicalScopeNodesStackStorage
      * @param {IVisitedLexicalScopeNodesStackStorage} visitedLexicalScopeNodesStackStorage
      * @param {IStringArrayStorage} stringArrayStorage
      * @param {IStringArrayStorage} stringArrayStorage
@@ -105,12 +98,11 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
      * @param {IStringArrayScopeCallsWrapperLexicalScopeDataStorage} stringArrayScopeCallsWrapperLexicalScopeDataStorage
      * @param {IStringArrayScopeCallsWrapperLexicalScopeDataStorage} stringArrayScopeCallsWrapperLexicalScopeDataStorage
      * @param {IStringArrayStorageAnalyzer} stringArrayStorageAnalyzer
      * @param {IStringArrayStorageAnalyzer} stringArrayStorageAnalyzer
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
-     * @param {TStringArrayTransformerCustomNodeFactory} stringArrayTransformerCustomNodeFactory
+     * @param {TStringArrayCustomNodeFactory} stringArrayTransformerCustomNodeFactory
      */
      */
     public constructor (
     public constructor (
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
         @inject(ServiceIdentifiers.IOptions) options: IOptions,
         @inject(ServiceIdentifiers.IOptions) options: IOptions,
-        @inject(ServiceIdentifiers.IEscapeSequenceEncoder) escapeSequenceEncoder: IEscapeSequenceEncoder,
         @inject(ServiceIdentifiers.ILiteralNodesCacheStorage) literalNodesCacheStorage: ILiteralNodesCacheStorage,
         @inject(ServiceIdentifiers.ILiteralNodesCacheStorage) literalNodesCacheStorage: ILiteralNodesCacheStorage,
         @inject(ServiceIdentifiers.IVisitedLexicalScopeNodesStackStorage) visitedLexicalScopeNodesStackStorage: IVisitedLexicalScopeNodesStackStorage,
         @inject(ServiceIdentifiers.IVisitedLexicalScopeNodesStackStorage) visitedLexicalScopeNodesStackStorage: IVisitedLexicalScopeNodesStackStorage,
         @inject(ServiceIdentifiers.IStringArrayStorage) stringArrayStorage: IStringArrayStorage,
         @inject(ServiceIdentifiers.IStringArrayStorage) stringArrayStorage: IStringArrayStorage,
@@ -121,12 +113,11 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
         @inject(ServiceIdentifiers.IStringArrayStorageAnalyzer) stringArrayStorageAnalyzer: IStringArrayStorageAnalyzer,
         @inject(ServiceIdentifiers.IStringArrayStorageAnalyzer) stringArrayStorageAnalyzer: IStringArrayStorageAnalyzer,
         @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
         @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
             identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory,
             identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory,
-        @inject(ServiceIdentifiers.Factory__IStringArrayTransformerCustomNode)
-            stringArrayTransformerCustomNodeFactory: TStringArrayTransformerCustomNodeFactory
+        @inject(ServiceIdentifiers.Factory__IStringArrayCustomNode)
+            stringArrayTransformerCustomNodeFactory: TStringArrayCustomNodeFactory
     ) {
     ) {
         super(randomGenerator, options);
         super(randomGenerator, options);
 
 
-        this.escapeSequenceEncoder = escapeSequenceEncoder;
         this.literalNodesCacheStorage = literalNodesCacheStorage;
         this.literalNodesCacheStorage = literalNodesCacheStorage;
         this.visitedLexicalScopeNodesStackStorage = visitedLexicalScopeNodesStackStorage;
         this.visitedLexicalScopeNodesStackStorage = visitedLexicalScopeNodesStackStorage;
         this.stringArrayStorage = stringArrayStorage;
         this.stringArrayStorage = stringArrayStorage;
@@ -156,15 +147,6 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
                     }
                     }
                 };
                 };
 
 
-            case NodeTransformationStage.Finalizing:
-                return {
-                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
-                        if (parentNode && NodeGuards.isLiteralNode(node)) {
-                            return this.encodeLiteralNodeToEscapeSequence(node, parentNode);
-                        }
-                    }
-                };
-
             default:
             default:
                 return null;
                 return null;
         }
         }
@@ -191,7 +173,10 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
      * @returns {NodeGuards}
      * @returns {NodeGuards}
      */
      */
     public transformNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): ESTree.Node {
     public transformNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): ESTree.Node {
-        if (typeof literalNode.value !== 'string' || NodeLiteralUtils.isProhibitedLiteralNode(literalNode, parentNode)) {
+        if (
+            !NodeLiteralUtils.isStringLiteralNode(literalNode)
+            || NodeLiteralUtils.isProhibitedLiteralNode(literalNode, parentNode)
+        ) {
             return literalNode;
             return literalNode;
         }
         }
 
 
@@ -208,7 +193,7 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
 
 
         const resultNode: ESTree.Node = stringArrayStorageItemData
         const resultNode: ESTree.Node = stringArrayStorageItemData
             ? this.getStringArrayCallNode(stringArrayStorageItemData)
             ? this.getStringArrayCallNode(stringArrayStorageItemData)
-            : this.getLiteralNode(literalValue);
+            : literalNode;
 
 
         this.literalNodesCacheStorage.set(cacheKey, resultNode);
         this.literalNodesCacheStorage.set(cacheKey, resultNode);
 
 
@@ -217,14 +202,6 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
         return resultNode;
         return resultNode;
     }
     }
 
 
-    /**
-     * @param {string} value
-     * @returns {Node}
-     */
-    private getLiteralNode (value: string): ESTree.Node {
-        return NodeFactory.literalNode(value);
-    }
-
     /**
     /**
      * @param {IStringArrayStorageItemData} stringArrayStorageItemData
      * @param {IStringArrayStorageItemData} stringArrayStorageItemData
      * @returns {Node}
      * @returns {Node}
@@ -234,7 +211,7 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
         const {decodeKey } = stringArrayStorageItemData;
         const {decodeKey } = stringArrayStorageItemData;
 
 
         const stringArrayCallCustomNode: ICustomNode<TInitialData<StringArrayCallNode>> =
         const stringArrayCallCustomNode: ICustomNode<TInitialData<StringArrayCallNode>> =
-            this.stringArrayTransformerCustomNodeFactory(StringArrayTransformerCustomNode.StringArrayCallNode);
+            this.stringArrayTransformerCustomNodeFactory(StringArrayCustomNode.StringArrayCallNode);
 
 
         stringArrayCallCustomNode.initialize(stringArrayCallsWrapperName, index, decodeKey);
         stringArrayCallCustomNode.initialize(stringArrayCallsWrapperName, index, decodeKey);
 
 
@@ -403,22 +380,4 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
 
 
         return lexicalScopeData;
         return lexicalScopeData;
     }
     }
-
-    /**
-     * @param {Literal} literalNode
-     * @param {Node} parentNode
-     * @returns {Literal}
-     */
-    private encodeLiteralNodeToEscapeSequence (
-        literalNode: ESTree.Literal,
-        parentNode: ESTree.Node
-    ): ESTree.Literal {
-        if (typeof literalNode.value !== 'string') {
-            return literalNode;
-        }
-
-        return NodeFactory.literalNode(
-            this.escapeSequenceEncoder.encode(literalNode.value, this.options.unicodeEscapeSequence)
-        );
-    }
 }
 }

+ 8 - 0
src/node/NodeGuards.ts

@@ -141,6 +141,14 @@ export class NodeGuards {
             && !('directive' in node);
             && !('directive' in node);
     }
     }
 
 
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isForOfStatementNode (node: ESTree.Node): node is ESTree.ForOfStatement {
+        return node.type === NodeType.ForOfStatement;
+    }
+
     /**
     /**
      * @param {Node} node
      * @param {Node} node
      * @returns {boolean}
      * @returns {boolean}

+ 8 - 0
src/node/NodeLiteralUtils.ts

@@ -3,6 +3,14 @@ import * as ESTree from 'estree';
 import { NodeGuards } from './NodeGuards';
 import { NodeGuards } from './NodeGuards';
 
 
 export class NodeLiteralUtils {
 export class NodeLiteralUtils {
+    /**
+     * @param {Literal} literalNode
+     * @returns {literalNode is (SimpleLiteral & {value: string})}
+     */
+    public static isStringLiteralNode (literalNode: ESTree.Literal): literalNode is ESTree.Literal & {value: string} {
+        return typeof literalNode.value === 'string';
+    }
+
     /**
     /**
      * @param {Literal} literalNode
      * @param {Literal} literalNode
      * @param {Node} parentNode
      * @param {Node} parentNode

+ 8 - 0
src/node/NodeMetadata.ts

@@ -20,6 +20,14 @@ export class NodeMetadata {
             : undefined;
             : undefined;
     }
     }
 
 
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isForceTransformNode (node: ESTree.Node): boolean {
+        return NodeMetadata.get(node, 'forceTransformNode') === true;
+    }
+
     /**
     /**
      * @param {Node} node
      * @param {Node} node
      * @returns {boolean}
      * @returns {boolean}

+ 2 - 2
src/node/NodeUtils.ts

@@ -131,13 +131,13 @@ export class NodeUtils {
                     return;
                     return;
                 }
                 }
 
 
-                const value: T[keyof T] = node[property];
+                const value: T[keyof T] | T[keyof T][] | null = node[property] ?? null;
 
 
                 let clonedValue: T[keyof T] | T[keyof T][] | null;
                 let clonedValue: T[keyof T] | T[keyof T][] | null;
 
 
                 if (value === null || value instanceof RegExp) {
                 if (value === null || value instanceof RegExp) {
                     clonedValue = value;
                     clonedValue = value;
-                } else if (Array.isArray(value)) {
+                } else if (value instanceof Array) {
                     clonedValue = value.map(NodeUtils.cloneRecursive);
                     clonedValue = value.map(NodeUtils.cloneRecursive);
                 } else if (typeof value === 'object') {
                 } else if (typeof value === 'object') {
                     clonedValue = NodeUtils.cloneRecursive(value);
                     clonedValue = NodeUtils.cloneRecursive(value);

+ 10 - 0
src/options/Options.ts

@@ -128,6 +128,16 @@ export class Options implements IOptions {
     ])
     ])
     public readonly domainLock!: string[];
     public readonly domainLock!: string[];
 
 
+    /**
+     * @type {string[]}
+     */
+    @IsArray()
+    @ArrayUnique()
+    @IsString({
+        each: true
+    })
+    public readonly forceTransformStrings!: string[];
+
     /**
     /**
      * @type {IdentifierNamesGenerator}
      * @type {IdentifierNamesGenerator}
      */
      */

+ 0 - 2
src/options/OptionsNormalizer.ts

@@ -17,7 +17,6 @@ import { SourceMapFileNameRule } from './normalizer-rules/SourceMapFileNameRule'
 import { SplitStringsChunkLengthRule } from './normalizer-rules/SplitStringsChunkLengthRule';
 import { SplitStringsChunkLengthRule } from './normalizer-rules/SplitStringsChunkLengthRule';
 import { StringArrayRule } from './normalizer-rules/StringArrayRule';
 import { StringArrayRule } from './normalizer-rules/StringArrayRule';
 import { StringArrayEncodingRule } from './normalizer-rules/StringArrayEncodingRule';
 import { StringArrayEncodingRule } from './normalizer-rules/StringArrayEncodingRule';
-import { StringArrayThresholdRule } from './normalizer-rules/StringArrayThresholdRule';
 import { StringArrayWrappersChainedCallsRule } from './normalizer-rules/StringArrayWappersChainedCalls';
 import { StringArrayWrappersChainedCallsRule } from './normalizer-rules/StringArrayWappersChainedCalls';
 
 
 @injectable()
 @injectable()
@@ -38,7 +37,6 @@ export class OptionsNormalizer implements IOptionsNormalizer {
         SplitStringsChunkLengthRule,
         SplitStringsChunkLengthRule,
         StringArrayRule,
         StringArrayRule,
         StringArrayEncodingRule,
         StringArrayEncodingRule,
-        StringArrayThresholdRule,
         StringArrayWrappersChainedCallsRule,
         StringArrayWrappersChainedCallsRule,
     ];
     ];
 
 

+ 0 - 27
src/options/normalizer-rules/StringArrayThresholdRule.ts

@@ -1,27 +0,0 @@
-import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRule';
-
-import { IOptions } from '../../interfaces/options/IOptions';
-
-import { StringArrayEncoding } from '../../enums/node-transformers/string-array-transformers/StringArrayEncoding';
-
-/**
- * @param {IOptions} options
- * @returns {IOptions}
- */
-export const StringArrayThresholdRule: TOptionsNormalizerRule = (options: IOptions): IOptions => {
-    if (options.stringArrayThreshold === 0) {
-        options = {
-            ...options,
-            rotateStringArray: false,
-            stringArray: false,
-            stringArrayEncoding: [
-                StringArrayEncoding.None
-            ],
-            stringArrayWrappersChainedCalls: false,
-            stringArrayWrappersCount: 0,
-            stringArrayThreshold: 0
-        };
-    }
-
-    return options;
-};

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

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

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

@@ -17,6 +17,7 @@ export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({
     disableConsoleOutput: false,
     disableConsoleOutput: false,
     domainLock: [],
     domainLock: [],
     exclude: [],
     exclude: [],
+    forceTransformStrings: [],
     identifierNamesGenerator: IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator,
     identifierNamesGenerator: IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator,
     identifiersPrefix: '',
     identifiersPrefix: '',
     identifiersDictionary: [],
     identifiersDictionary: [],

+ 1 - 25
src/storages/string-array-transformers/StringArrayStorage.ts

@@ -7,7 +7,6 @@ import { TStringArrayEncoding } from '../../types/options/TStringArrayEncoding';
 import { IArrayUtils } from '../../interfaces/utils/IArrayUtils';
 import { IArrayUtils } from '../../interfaces/utils/IArrayUtils';
 import { ICryptUtilsSwappedAlphabet } from '../../interfaces/utils/ICryptUtilsSwappedAlphabet';
 import { ICryptUtilsSwappedAlphabet } from '../../interfaces/utils/ICryptUtilsSwappedAlphabet';
 import { IEncodedValue } from '../../interfaces/IEncodedValue';
 import { IEncodedValue } from '../../interfaces/IEncodedValue';
-import { IEscapeSequenceEncoder } from '../../interfaces/utils/IEscapeSequenceEncoder';
 import { IIdentifierNamesGenerator } from '../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
 import { IIdentifierNamesGenerator } from '../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
@@ -55,11 +54,6 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
      */
      */
     private readonly cryptUtilsSwappedAlphabet: ICryptUtilsSwappedAlphabet;
     private readonly cryptUtilsSwappedAlphabet: ICryptUtilsSwappedAlphabet;
 
 
-    /**
-     * @type {IEscapeSequenceEncoder}
-     */
-    private readonly escapeSequenceEncoder: IEscapeSequenceEncoder;
-
     /**
     /**
      * @type {IIdentifierNamesGenerator}
      * @type {IIdentifierNamesGenerator}
      */
      */
@@ -96,7 +90,6 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
      * @param {IRandomGenerator} randomGenerator
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
      * @param {IOptions} options
      * @param {ICryptUtilsSwappedAlphabet} cryptUtilsSwappedAlphabet
      * @param {ICryptUtilsSwappedAlphabet} cryptUtilsSwappedAlphabet
-     * @param {IEscapeSequenceEncoder} escapeSequenceEncoder
      */
      */
     public constructor (
     public constructor (
         @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
         @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
@@ -104,15 +97,13 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
         @inject(ServiceIdentifiers.IArrayUtils) arrayUtils: IArrayUtils,
         @inject(ServiceIdentifiers.IArrayUtils) arrayUtils: IArrayUtils,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
         @inject(ServiceIdentifiers.IOptions) options: IOptions,
         @inject(ServiceIdentifiers.IOptions) options: IOptions,
-        @inject(ServiceIdentifiers.ICryptUtilsSwappedAlphabet) cryptUtilsSwappedAlphabet: ICryptUtilsSwappedAlphabet,
-        @inject(ServiceIdentifiers.IEscapeSequenceEncoder) escapeSequenceEncoder: IEscapeSequenceEncoder
+        @inject(ServiceIdentifiers.ICryptUtilsSwappedAlphabet) cryptUtilsSwappedAlphabet: ICryptUtilsSwappedAlphabet
     ) {
     ) {
         super(randomGenerator, options);
         super(randomGenerator, options);
 
 
         this.identifierNamesGenerator = identifierNamesGeneratorFactory(options);
         this.identifierNamesGenerator = identifierNamesGeneratorFactory(options);
         this.arrayUtils = arrayUtils;
         this.arrayUtils = arrayUtils;
         this.cryptUtilsSwappedAlphabet = cryptUtilsSwappedAlphabet;
         this.cryptUtilsSwappedAlphabet = cryptUtilsSwappedAlphabet;
-        this.escapeSequenceEncoder = escapeSequenceEncoder;
 
 
         this.rc4Keys = this.randomGenerator.getRandomGenerator()
         this.rc4Keys = this.randomGenerator.getRandomGenerator()
             .n(
             .n(
@@ -225,21 +216,6 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
         );
         );
     }
     }
 
 
-    /**
-     * @returns {string}
-     */
-    public toString (): string {
-        return Array
-            .from(this.storage.values())
-            .map((stringArrayStorageItemData: IStringArrayStorageItemData) => {
-                // we have to encode here, because of possible errors during `parse` of StringArrayCustomNode
-                return `'${this.escapeSequenceEncoder.encode(
-                    stringArrayStorageItemData.encodedValue,
-                    this.options.unicodeEscapeSequence
-                )}'`;
-            }).toString();
-    }
-
     /**
     /**
      * @param {string} value
      * @param {string} value
      * @returns {IStringArrayStorageItemData}
      * @returns {IStringArrayStorageItemData}

+ 7 - 0
src/types/container/custom-nodes/TStringArrayCustomNodeFactory.ts

@@ -0,0 +1,7 @@
+import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
+
+import { StringArrayCustomNode } from '../../../enums/custom-nodes/StringArrayCustomNode';
+
+export type TStringArrayCustomNodeFactory = <
+    TInitialData extends unknown[] = unknown[]
+> (stringArrayCustomNodeName: StringArrayCustomNode) => ICustomNode <TInitialData>;

+ 0 - 7
src/types/container/custom-nodes/TStringArrayTransformerCustomNodeFactory.ts

@@ -1,7 +0,0 @@
-import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
-
-import { StringArrayTransformerCustomNode } from '../../../enums/custom-nodes/StringArrayTransformerCustomNode';
-
-export type TStringArrayTransformerCustomNodeFactory = <
-    TInitialData extends unknown[] = unknown[]
-> (stringArrayTransformerCustomNodeName: StringArrayTransformerCustomNode) => ICustomNode <TInitialData>;

+ 0 - 3
src/types/node/TNodeGuard.ts

@@ -1,3 +0,0 @@
-import * as ESTree from 'estree';
-
-export type TNodeGuard = (node: ESTree.Node) => boolean;

+ 5 - 0
src/types/node/TObfuscatingGuard.ts

@@ -0,0 +1,5 @@
+import * as ESTree from 'estree';
+
+import { ObfuscatingGuardResult } from '../../enums/node/ObfuscatingGuardResult';
+
+export type TObfuscatingGuard = (node: ESTree.Node) => ObfuscatingGuardResult;

+ 11 - 0
src/utils/StringUtils.ts

@@ -0,0 +1,11 @@
+import jsStringEscape from 'js-string-escape';
+
+export class StringUtils {
+    /**
+     * @param {string} string
+     * @returns {string}
+     */
+    public static escapeJsString (string: string): string {
+        return jsStringEscape(string);
+    }
+}

+ 1 - 2
test/declarations/index.d.ts

@@ -1,3 +1,2 @@
 /// <reference path="../../src/declarations/escodegen.d.ts" />
 /// <reference path="../../src/declarations/escodegen.d.ts" />
-/// <reference path="../../src/declarations/ESTree.d.ts" />
-/// <reference path="../../src/declarations/js-string-escape.d.ts" />
+/// <reference path="../../src/declarations/ESTree.d.ts" />

+ 4 - 9
test/dev/dev.ts

@@ -2,18 +2,15 @@
 
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNodes';
 import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNodes';
 import { IdentifierNamesGenerator } from '../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
 import { IdentifierNamesGenerator } from '../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
-import { StringArrayWrappersType } from '../../src/enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 
 
 (function () {
 (function () {
     const JavaScriptObfuscator: any = require('../../index');
     const JavaScriptObfuscator: any = require('../../index');
 
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
         `
-            const foo = 'foo';
-
-            function test () {
-                const bar = 'bar';
-            }
+           var test = '\\nreturn \\n//# sourceURL= there can only be \\'^\\' and \\'!\\' markers in a subscription marble diagram.';
+           
+           console.log(test);
         `,
         `,
         {
         {
             ...NO_ADDITIONAL_NODES_PRESET,
             ...NO_ADDITIONAL_NODES_PRESET,
@@ -21,9 +18,7 @@ import { StringArrayWrappersType } from '../../src/enums/node-transformers/strin
             identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
             identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
             stringArray: true,
             stringArray: true,
             stringArrayThreshold: 1,
             stringArrayThreshold: 1,
-            stringArrayWrappersChainedCalls: true,
-            stringArrayWrappersCount: 1,
-            stringArrayWrappersType: StringArrayWrappersType.Function
+            unicodeEscapeSequence: false
         }
         }
     ).getObfuscatedCode();
     ).getObfuscatedCode();
 
 

+ 47 - 47
test/functional-tests/cli/JavaScriptObfuscatorCLI.spec.ts

@@ -15,15 +15,15 @@ describe('JavaScriptObfuscatorCLI', function (): void {
 
 
     const expectedError: RegExp = /Given input path must be a valid/;
     const expectedError: RegExp = /Given input path must be a valid/;
 
 
-    const fixturesDirName: string = 'test/fixtures';
+    const fixturesDirName: string = path.join('test', 'fixtures');
     const fixtureFileName: string = 'sample.js';
     const fixtureFileName: string = 'sample.js';
-    const fixtureFilePath: string = `${fixturesDirName}/${fixtureFileName}`;
-    const outputDirName: string = 'test/tmp';
+    const fixtureFilePath: string = path.join(fixturesDirName, fixtureFileName);
+    const outputDirName: string = path.join('test', 'tmp');
     const outputFileName: string = 'sample-obfuscated.js';
     const outputFileName: string = 'sample-obfuscated.js';
-    const outputFilePath: string = `${outputDirName}/${outputFileName}`;
-    const configDirName: string = 'test/fixtures';
+    const outputFilePath: string = path.join(outputDirName, outputFileName);
+    const configDirName: string = path.join('test', 'fixtures');
     const configFileName: string = 'config.js';
     const configFileName: string = 'config.js';
-    const configFilePath: string = `${configDirName}/${configFileName}`;
+    const configFilePath: string = path.join(configDirName, configFileName);
 
 
 
 
     describe('run', () => {
     describe('run', () => {
@@ -66,7 +66,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         isFileExist: boolean;
                         isFileExist: boolean;
 
 
                     before(() => {
                     before(() => {
-                        outputFixturesFilePath = `${fixturesDirName}/${outputFileName}`;
+                        outputFixturesFilePath = path.join(fixturesDirName, outputFileName);
 
 
                         JavaScriptObfuscatorCLI.obfuscate([
                         JavaScriptObfuscatorCLI.obfuscate([
                             'node',
                             'node',
@@ -93,7 +93,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         testFunc = () => JavaScriptObfuscatorCLI.obfuscate([
                         testFunc = () => JavaScriptObfuscatorCLI.obfuscate([
                             'node',
                             'node',
                             'javascript-obfuscator',
                             'javascript-obfuscator',
-                            'wrong/file/path'
+                            path.join('wrong', 'file', 'path')
                         ]);
                         ]);
                     });
                     });
 
 
@@ -105,7 +105,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                 describe('Variant #3: input file extension isn\'t `.js`', () => {
                 describe('Variant #3: input file extension isn\'t `.js`', () => {
                     const expectedError: RegExp = /Given input path must be a valid/;
                     const expectedError: RegExp = /Given input path must be a valid/;
                     const outputFileName: string = 'sample-obfuscated.ts';
                     const outputFileName: string = 'sample-obfuscated.ts';
-                    const outputFilePath: string = `${outputDirName}/${outputFileName}`;
+                    const outputFilePath: string = path.join(outputDirName, outputFileName);
 
 
                     let testFunc: () => void;
                     let testFunc: () => void;
 
 
@@ -141,7 +141,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                             '--output',
                             '--output',
                             outputFilePath,
                             outputFilePath,
                             '--exclude',
                             '--exclude',
-                            '**/foo.js'
+                            path.join('**', 'foo.js')
                         ]);
                         ]);
 
 
                         isFileExist = fs.existsSync(outputFilePath);
                         isFileExist = fs.existsSync(outputFilePath);
@@ -163,7 +163,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                             '--output',
                             '--output',
                             outputFilePath,
                             outputFilePath,
                             '--exclude',
                             '--exclude',
-                            '**/sample.js'
+                            path.join('**', 'sample.js')
                         ]);
                         ]);
                     });
                     });
 
 
@@ -180,7 +180,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
 
 
         describe('Variant #2: obfuscation of directory', () => {
         describe('Variant #2: obfuscation of directory', () => {
             describe(`Variant #1: default behaviour`, () => {
             describe(`Variant #1: default behaviour`, () => {
-                const directoryPath: string = `${fixturesDirName}/directory-obfuscation`;
+                const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation');
                 const outputFileName1: string = 'foo-obfuscated.js';
                 const outputFileName1: string = 'foo-obfuscated.js';
                 const outputFileName2: string = 'bar-obfuscated.js';
                 const outputFileName2: string = 'bar-obfuscated.js';
                 const outputFileName3: string = 'baz-obfuscated.js';
                 const outputFileName3: string = 'baz-obfuscated.js';
@@ -198,9 +198,9 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                     fileContent2: string;
                     fileContent2: string;
 
 
                 before(() => {
                 before(() => {
-                    outputFixturesFilePath1 = `${directoryPath}/${outputFileName1}`;
-                    outputFixturesFilePath2 = `${directoryPath}/${outputFileName2}`;
-                    outputFixturesFilePath3 = `${directoryPath}/${outputFileName3}`;
+                    outputFixturesFilePath1 = path.join(directoryPath, outputFileName1);
+                    outputFixturesFilePath2 = path.join(directoryPath, outputFileName2);
+                    outputFixturesFilePath3 = path.join(directoryPath, outputFileName3);
 
 
                     JavaScriptObfuscatorCLI.obfuscate([
                     JavaScriptObfuscatorCLI.obfuscate([
                         'node',
                         'node',
@@ -245,7 +245,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
             });
             });
 
 
             describe('Variant #2: obfuscation of directory with `identifiersPrefix` option value', () => {
             describe('Variant #2: obfuscation of directory with `identifiersPrefix` option value', () => {
-                const directoryPath: string = `${fixturesDirName}/directory-obfuscation`;
+                const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation');
                 const identifiersPrefix: string = 'foo';
                 const identifiersPrefix: string = 'foo';
                 const outputFileName1: string = 'foo-obfuscated.js';
                 const outputFileName1: string = 'foo-obfuscated.js';
                 const outputFileName2: string = 'bar-obfuscated.js';
                 const outputFileName2: string = 'bar-obfuscated.js';
@@ -261,8 +261,8 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                     fileContent2: string;
                     fileContent2: string;
 
 
                 before(() => {
                 before(() => {
-                    outputFixturesFilePath1 = `${directoryPath}/${outputFileName1}`;
-                    outputFixturesFilePath2 = `${directoryPath}/${outputFileName2}`;
+                    outputFixturesFilePath1 = path.join(directoryPath, outputFileName1);
+                    outputFixturesFilePath2 = path.join(directoryPath, outputFileName2);
 
 
                     JavaScriptObfuscatorCLI.obfuscate([
                     JavaScriptObfuscatorCLI.obfuscate([
                         'node',
                         'node',
@@ -304,9 +304,9 @@ describe('JavaScriptObfuscatorCLI', function (): void {
             });
             });
 
 
             describe('Variant #3: obfuscation of directory with `output` option', () => {
             describe('Variant #3: obfuscation of directory with `output` option', () => {
-                const directoryPath: string = `${fixturesDirName}/directory-obfuscation`;
+                const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation');
                 const outputDirectoryName: string = 'obfuscated';
                 const outputDirectoryName: string = 'obfuscated';
-                const outputDirectoryPath: string = `${directoryPath}/${outputDirectoryName}`;
+                const outputDirectoryPath: string = path.join(directoryPath, outputDirectoryName);
                 const outputFileName1: string = 'foo.js';
                 const outputFileName1: string = 'foo.js';
                 const outputFileName2: string = 'bar.js';
                 const outputFileName2: string = 'bar.js';
                 const outputFileName3: string = 'baz.js';
                 const outputFileName3: string = 'baz.js';
@@ -319,9 +319,9 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                     isFileExist3: boolean;
                     isFileExist3: boolean;
 
 
                 before(() => {
                 before(() => {
-                    outputFixturesFilePath1 = `${outputDirectoryPath}/${directoryPath}/${outputFileName1}`;
-                    outputFixturesFilePath2 = `${outputDirectoryPath}/${directoryPath}/${outputFileName2}`;
-                    outputFixturesFilePath3 = `${outputDirectoryPath}/${directoryPath}/${outputFileName3}`;
+                    outputFixturesFilePath1 = path.join(outputDirectoryPath, outputFileName1);
+                    outputFixturesFilePath2 = path.join(outputDirectoryPath, outputFileName2);
+                    outputFixturesFilePath3 = path.join(outputDirectoryPath, outputFileName3);
 
 
                     JavaScriptObfuscatorCLI.obfuscate([
                     JavaScriptObfuscatorCLI.obfuscate([
                         'node',
                         'node',
@@ -338,7 +338,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
 
 
                 it(
                 it(
                     `should create file \`${outputFileName1}\` with obfuscated code in ` +
                     `should create file \`${outputFileName1}\` with obfuscated code in ` +
-                    `\`${fixturesDirName}/${outputDirectoryName}\` directory`,
+                    `\`${path.join(fixturesDirName, outputDirectoryName)}\` directory`,
                     () => {
                     () => {
                         assert.equal(isFileExist1, true);
                         assert.equal(isFileExist1, true);
                     }
                     }
@@ -346,7 +346,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
 
 
                 it(
                 it(
                     `should create file \`${outputFileName2}\` with obfuscated code in ` +
                     `should create file \`${outputFileName2}\` with obfuscated code in ` +
-                    `\`${fixturesDirName}/${outputDirectoryName}\` directory`,
+                    `\`${path.join(fixturesDirName, outputDirectoryName)}\` directory`,
                     () => {
                     () => {
                         assert.equal(isFileExist2, true);
                         assert.equal(isFileExist2, true);
                     }
                     }
@@ -354,7 +354,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
 
 
                 it(
                 it(
                     `shouldn't create file \`${outputFileName3}\` in ` +
                     `shouldn't create file \`${outputFileName3}\` in ` +
-                    `\`${fixturesDirName}/${outputDirectoryName}\` directory`,
+                    `\`${path.join(fixturesDirName, outputDirectoryName)}\` directory`,
                     () => {
                     () => {
                         assert.equal(isFileExist3, false);
                         assert.equal(isFileExist3, false);
                     }
                     }
@@ -367,7 +367,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
 
 
             describe('Variant #4: --exclude option', () => {
             describe('Variant #4: --exclude option', () => {
                 describe('Variant #1: --exclude option is pointed on different file', () => {
                 describe('Variant #1: --exclude option is pointed on different file', () => {
-                    const directoryPath: string = `${fixturesDirName}/directory-obfuscation`;
+                    const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation');
                     const outputFileName1: string = 'foo-obfuscated.js';
                     const outputFileName1: string = 'foo-obfuscated.js';
                     const outputFileName2: string = 'bar-obfuscated.js';
                     const outputFileName2: string = 'bar-obfuscated.js';
                     const outputFileName3: string = 'baz-obfuscated.js';
                     const outputFileName3: string = 'baz-obfuscated.js';
@@ -385,16 +385,16 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         fileContent2: string;
                         fileContent2: string;
 
 
                     before(() => {
                     before(() => {
-                        outputFixturesFilePath1 = `${directoryPath}/${outputFileName1}`;
-                        outputFixturesFilePath2 = `${directoryPath}/${outputFileName2}`;
-                        outputFixturesFilePath3 = `${directoryPath}/${outputFileName3}`;
+                        outputFixturesFilePath1 = path.join(directoryPath, outputFileName1);
+                        outputFixturesFilePath2 = path.join(directoryPath, outputFileName2);
+                        outputFixturesFilePath3 = path.join(directoryPath, outputFileName3);
 
 
                         JavaScriptObfuscatorCLI.obfuscate([
                         JavaScriptObfuscatorCLI.obfuscate([
                             'node',
                             'node',
                             'javascript-obfuscator',
                             'javascript-obfuscator',
                             directoryPath,
                             directoryPath,
                             '--exclude',
                             '--exclude',
-                            '**/bark.js',
+                            path.join('**', 'bark.js'),
                             '--rename-globals',
                             '--rename-globals',
                             'true'
                             'true'
                         ]);
                         ]);
@@ -434,7 +434,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                 });
                 });
 
 
                 describe('Variant #2: --exclude option is pointed on file under obfuscating directory', () => {
                 describe('Variant #2: --exclude option is pointed on file under obfuscating directory', () => {
-                    const directoryPath: string = `${fixturesDirName}/directory-obfuscation`;
+                    const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation');
                     const outputFileName1: string = 'foo-obfuscated.js';
                     const outputFileName1: string = 'foo-obfuscated.js';
                     const outputFileName2: string = 'bar-obfuscated.js';
                     const outputFileName2: string = 'bar-obfuscated.js';
                     const outputFileName3: string = 'baz-obfuscated.js';
                     const outputFileName3: string = 'baz-obfuscated.js';
@@ -450,16 +450,16 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         fileContent1: string;
                         fileContent1: string;
 
 
                     before(() => {
                     before(() => {
-                        outputFixturesFilePath1 = `${directoryPath}/${outputFileName1}`;
-                        outputFixturesFilePath2 = `${directoryPath}/${outputFileName2}`;
-                        outputFixturesFilePath3 = `${directoryPath}/${outputFileName3}`;
+                        outputFixturesFilePath1 = path.join(directoryPath, outputFileName1);
+                        outputFixturesFilePath2 = path.join(directoryPath, outputFileName2);
+                        outputFixturesFilePath3 = path.join(directoryPath, outputFileName3);
 
 
                         JavaScriptObfuscatorCLI.obfuscate([
                         JavaScriptObfuscatorCLI.obfuscate([
                             'node',
                             'node',
                             'javascript-obfuscator',
                             'javascript-obfuscator',
                             directoryPath,
                             directoryPath,
                             '--exclude',
                             '--exclude',
-                            '**/foo.js',
+                            path.join('**', 'foo.js'),
                             '--rename-globals',
                             '--rename-globals',
                             'true'
                             'true'
                         ]);
                         ]);
@@ -619,7 +619,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                     const expectedSourceMapSourceName: string = path.basename(fixtureFileName);
                     const expectedSourceMapSourceName: string = path.basename(fixtureFileName);
                     const sourceMapFileName: string = 'test';
                     const sourceMapFileName: string = 'test';
                     const sourceMapFilePath: string = `${sourceMapFileName}.js.map`;
                     const sourceMapFilePath: string = `${sourceMapFileName}.js.map`;
-                    const outputSourceMapFilePath: string = `${outputDirName}/${sourceMapFilePath}`;
+                    const outputSourceMapFilePath: string = path.join(outputDirName, sourceMapFilePath);
 
 
                     let isFileExist: boolean,
                     let isFileExist: boolean,
                         sourceMapObject: any;
                         sourceMapObject: any;
@@ -864,7 +864,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
             });
             });
 
 
             describe('`--exclude` option', () => {
             describe('`--exclude` option', () => {
-                const directoryPath: string = `${fixturesDirName}/directory-obfuscation`;
+                const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation');
                 const outputFileName1: string = 'foo-obfuscated.js';
                 const outputFileName1: string = 'foo-obfuscated.js';
                 const outputFileName2: string = 'bar-obfuscated.js';
                 const outputFileName2: string = 'bar-obfuscated.js';
 
 
@@ -874,8 +874,8 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                     isFileExist2: boolean;
                     isFileExist2: boolean;
 
 
                 before(() => {
                 before(() => {
-                    outputFixturesFilePath1 = `${directoryPath}/${outputFileName1}`;
-                    outputFixturesFilePath2 = `${directoryPath}/${outputFileName2}`;
+                    outputFixturesFilePath1 = path.join(directoryPath, outputFileName1);
+                    outputFixturesFilePath2 = path.join(directoryPath, outputFileName2);
 
 
                     JavaScriptObfuscatorCLI.obfuscate([
                     JavaScriptObfuscatorCLI.obfuscate([
                         'node',
                         'node',
@@ -942,17 +942,17 @@ describe('JavaScriptObfuscatorCLI', function (): void {
 
 
         describe('Logging', () => {
         describe('Logging', () => {
             describe('Obfuscating file message', () => {
             describe('Obfuscating file message', () => {
-                const directoryPath: string = `${fixturesDirName}/directory-obfuscation`;
+                const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation');
 
 
                 const inputFileName1: string = 'foo.js';
                 const inputFileName1: string = 'foo.js';
                 const inputFileName2: string = 'bar.js';
                 const inputFileName2: string = 'bar.js';
-                const inputFilePath1: string = `${directoryPath}/${inputFileName1}`;
-                const inputFilePath2: string = `${directoryPath}/${inputFileName2}`;
+                const inputFilePath1: string = path.join(directoryPath, inputFileName1);
+                const inputFilePath2: string = path.join(directoryPath, inputFileName2);
 
 
                 const outputFileName1: string = 'foo-obfuscated.js';
                 const outputFileName1: string = 'foo-obfuscated.js';
                 const outputFileName2: string = 'bar-obfuscated.js';
                 const outputFileName2: string = 'bar-obfuscated.js';
-                const outputFilePath1: string = `${directoryPath}/${outputFileName1}`;
-                const outputFilePath2: string = `${directoryPath}/${outputFileName2}`;
+                const outputFilePath1: string = path.join(directoryPath, outputFileName1);
+                const outputFilePath2: string = path.join(directoryPath, outputFileName2);
 
 
                 const expectedLoggingMessage1: string = `[javascript-obfuscator-cli] Obfuscating file: ${inputFilePath1}...`;
                 const expectedLoggingMessage1: string = `[javascript-obfuscator-cli] Obfuscating file: ${inputFilePath1}...`;
                 const expectedLoggingMessage2: string = `[javascript-obfuscator-cli] Obfuscating file: ${inputFilePath2}...`;
                 const expectedLoggingMessage2: string = `[javascript-obfuscator-cli] Obfuscating file: ${inputFilePath2}...`;
@@ -992,10 +992,10 @@ describe('JavaScriptObfuscatorCLI', function (): void {
             });
             });
 
 
             describe('Error message', () => {
             describe('Error message', () => {
-                const directoryPath: string = `${fixturesDirName}/directory-obfuscation-error`;
+                const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation-error');
 
 
                 const inputFileName: string = 'foo.js';
                 const inputFileName: string = 'foo.js';
-                const inputFilePath: string = `${directoryPath}/${inputFileName}`;
+                const inputFilePath: string = path.join(directoryPath, inputFileName);
 
 
                 const expectedLoggingMessage1: string = `[javascript-obfuscator-cli] Error in file: ${inputFilePath}...`;
                 const expectedLoggingMessage1: string = `[javascript-obfuscator-cli] Error in file: ${inputFilePath}...`;
 
 

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

@@ -7,9 +7,11 @@ import { readFileAsString } from '../../../../helpers/readFileAsString';
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
 
 
 describe('HashbangOperatorTransformer', () => {
 describe('HashbangOperatorTransformer', () => {
+    const lineSeparator: string = '\r?\n';
+
     describe('Variant #1: simple', () => {
     describe('Variant #1: simple', () => {
         const regExp: RegExp = new RegExp(
         const regExp: RegExp = new RegExp(
-            `^#!\/usr\/bin\/env node\n` +
+            `^#!\/usr\/bin\/env node${lineSeparator}` +
             `var foo *= *'abc';`
             `var foo *= *'abc';`
         );
         );
 
 
@@ -33,7 +35,7 @@ describe('HashbangOperatorTransformer', () => {
 
 
     describe('Variant #2: multiple new lines', () => {
     describe('Variant #2: multiple new lines', () => {
         const regExp: RegExp = new RegExp(
         const regExp: RegExp = new RegExp(
-            `^#!\/usr\/bin\/env node\n\n\n\n` +
+            `^#!\/usr\/bin\/env node${lineSeparator.repeat(4)}` +
             `var foo *= *'abc';`
             `var foo *= *'abc';`
         );
         );
 
 
@@ -57,7 +59,7 @@ describe('HashbangOperatorTransformer', () => {
 
 
     describe('Variant #3: `stringArray` option enabled', () => {
     describe('Variant #3: `stringArray` option enabled', () => {
         const regExp: RegExp = new RegExp(
         const regExp: RegExp = new RegExp(
-            `^#!\/usr\/bin\/env node\n` +
+            `^#!\/usr\/bin\/env node${lineSeparator}` +
             `var _0x(\\w){4} *= *\\['abc'];`
             `var _0x(\\w){4} *= *\\['abc'];`
         );
         );
 
 

+ 46 - 0
test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts

@@ -869,6 +869,52 @@ describe('JavaScriptObfuscator', () => {
             });
             });
         });
         });
 
 
+        describe('Eval `Hello World`', function () {
+            this.timeout(20000);
+
+            const samplesCount: number = 100;
+            const expectedEvaluationResult: string = 'Hello World';
+            let isEvaluationSuccessful: boolean = true;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/eval-hello-world.js');
+
+                for (let i = 0; i < samplesCount; i++) {
+                    const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            compact: false,
+                            controlFlowFlattening: true,
+                            controlFlowFlatteningThreshold: 1,
+                            deadCodeInjection: true,
+                            deadCodeInjectionThreshold: 1,
+                            disableConsoleOutput: true,
+                            identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
+                            renameProperties: true,
+                            simplify: false,
+                            stringArray: true,
+                            stringArrayThreshold: 1,
+                            stringArrayWrappersChainedCalls: true,
+                            stringArrayWrappersCount: 1,
+                            stringArrayWrappersType: StringArrayWrappersType.Variable
+                        }
+                    ).getObfuscatedCode();
+
+                    const evaluationResult: string = eval(obfuscatedCode);
+
+                    if (evaluationResult !== expectedEvaluationResult) {
+                        isEvaluationSuccessful = false;
+                        break;
+                    }
+                }
+            });
+
+            it('should correctly evaluate obfuscated code', () => {
+                assert.equal(isEvaluationSuccessful, true);
+            });
+        });
+
         describe('Identifier names collision between base code and appended string array nodes', function () {
         describe('Identifier names collision between base code and appended string array nodes', function () {
             this.timeout(10000);
             this.timeout(10000);
 
 

+ 4 - 0
test/functional-tests/javascript-obfuscator/fixtures/eval-hello-world.js

@@ -0,0 +1,4 @@
+function helloWorld() {
+    return 'Hello World';
+}
+helloWorld();

+ 80 - 8
test/functional-tests/node-transformers/converting-transformers/split-string-transformer/SplitStringTransformer.spec.ts

@@ -1,5 +1,7 @@
 import { assert } from 'chai';
 import { assert } from 'chai';
 
 
+import { StringArrayEncoding } from '../../../../../src/enums/node-transformers/string-array-transformers/StringArrayEncoding';
+
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
 
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
@@ -166,7 +168,7 @@ describe('SplitStringTransformer', () => {
     describe('Variant #10: string with emoji', () => {
     describe('Variant #10: string with emoji', () => {
         describe('Variant #1: single emoji', () => {
         describe('Variant #1: single emoji', () => {
             it('should correctly split string with emoji', () => {
             it('should correctly split string with emoji', () => {
-                const regExp: RegExp = /^'a' *\+ *'b' *\+ *'\ud83d' *\+ *'\udc4b' *\+ *'\ud83c' *\+ *'\udffc' *\+ *'c' *\+ *'d';$/;
+                const regExp: RegExp = /^'a' *\+ *'b' *\+ *'👋🏼' *\+ *'c' *\+ *'d';$/;
 
 
                 const code: string = readFileAsString(__dirname + '/fixtures/string-with-emoji-1.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/string-with-emoji-1.js');
 
 
@@ -204,7 +206,7 @@ describe('SplitStringTransformer', () => {
 
 
         describe('Variant #2: multiple emoji', () => {
         describe('Variant #2: multiple emoji', () => {
             it('should correctly split string with emoji', () => {
             it('should correctly split string with emoji', () => {
-                const regExp: RegExp = /^'a' *\+ *'b' *\+ *'\ud83d' *\+ *'\ude34' *\+ *'\ud83d' *\+ *'\ude04' *\+ *'c' *\+ *'d';$/;
+                const regExp: RegExp = /^'a' *\+ *'b' *\+ *'😴' *\+ *'😄' *\+ *'c' *\+ *'d';$/;
 
 
                 const code: string = readFileAsString(__dirname + '/fixtures/string-with-emoji-2.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/string-with-emoji-2.js');
 
 
@@ -239,9 +241,79 @@ describe('SplitStringTransformer', () => {
                 assert.equal(resultString, expectedResultString);
                 assert.equal(resultString, expectedResultString);
             });
             });
         });
         });
+
+        describe('Variant #3: correct split emoji', () => {
+            it('should correctly split string with emoji', () => {
+                const regExp: RegExp = /^'ab👋🏼' *\+ *'cd';$/;
+
+                const code: string = readFileAsString(__dirname + '/fixtures/string-with-emoji-1.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        splitStrings: true,
+                        splitStringsChunkLength: 3
+                    }
+                ).getObfuscatedCode();
+
+                assert.match(obfuscatedCode,  regExp);
+            });
+
+            it('should correctly evaluate splitted string with emoji', () => {
+                const expectedResultString: string = 'ab👋🏼cd';
+
+                const code: string = readFileAsString(__dirname + '/fixtures/string-with-emoji-1.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        splitStrings: true,
+                        splitStringsChunkLength: 3
+                    }
+                ).getObfuscatedCode();
+
+                const resultString: string = eval(obfuscatedCode);
+
+                assert.equal(resultString, expectedResultString);
+            });
+        });
+    });
+
+    describe('Variant #11: Integration with `stringArrayEncoding` option', () => {
+        describe('Variant #1: base64 encoding', () => {
+            describe('Variant #1: string with emoji', () => {
+                describe('Variant #1: prevent URI-malformed error', () => {
+                    it('should correctly evaluate splitted string with emoji', () => {
+                        const expectedResultString: string = 'ab👋🏼cd';
+
+                        const code: string = readFileAsString(__dirname + '/fixtures/string-with-emoji-1.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                splitStrings: true,
+                                splitStringsChunkLength: 3,
+                                stringArray: true,
+                                stringArrayThreshold: 1,
+                                stringArrayEncoding: [
+                                    StringArrayEncoding.Base64
+                                ]
+                            }
+                        ).getObfuscatedCode();
+
+                        const resultString: string = eval(obfuscatedCode);
+
+                        assert.equal(resultString, expectedResultString);
+                    });
+                });
+            });
+        });
     });
     });
 
 
-    describe('Variant #11: Integration with `transformObjectKeys` option', () => {
+    describe('Variant #12: Integration with `transformObjectKeys` option', () => {
         it('should correctly transform string when `transformObjectKeys` option is enabled', () => {
         it('should correctly transform string when `transformObjectKeys` option is enabled', () => {
             const regExp: RegExp = new RegExp(`` +
             const regExp: RegExp = new RegExp(`` +
                 `var _0x[a-f0-9]{4,6} *= *{};` +
                 `var _0x[a-f0-9]{4,6} *= *{};` +
@@ -267,7 +339,7 @@ describe('SplitStringTransformer', () => {
         });
         });
     });
     });
 
 
-    describe('Variant #12: Integration with `reservedStrings` option', () => {
+    describe('Variant #13: Integration with `reservedStrings` option', () => {
         it('should correctly ignore strings from `reservedStrings` option', () => {
         it('should correctly ignore strings from `reservedStrings` option', () => {
             const code: string = readFileAsString(__dirname + '/fixtures/ignore-reserved-strings.js');
             const code: string = readFileAsString(__dirname + '/fixtures/ignore-reserved-strings.js');
 
 
@@ -288,7 +360,7 @@ describe('SplitStringTransformer', () => {
         });
         });
     });
     });
 
 
-    describe('Variant #13: Large string', () => {
+    describe('Variant #14: Large string', () => {
         it('Should does not throw `Maximum call stack size exceeded` error on a large string', () => {
         it('Should does not throw `Maximum call stack size exceeded` error on a large string', () => {
             const code: string = `var foo = '${'a'.repeat(10000)}';`;
             const code: string = `var foo = '${'a'.repeat(10000)}';`;
 
 
@@ -308,7 +380,7 @@ describe('SplitStringTransformer', () => {
         });
         });
     });
     });
 
 
-    describe('Variant #14: import declaration source literal', () => {
+    describe('Variant #15: import declaration source literal', () => {
         const importDeclarationRegExp: RegExp = /import *{ *bar *} *from *'foo';/;
         const importDeclarationRegExp: RegExp = /import *{ *bar *} *from *'foo';/;
 
 
         let obfuscatedCode: string;
         let obfuscatedCode: string;
@@ -331,7 +403,7 @@ describe('SplitStringTransformer', () => {
         });
         });
     });
     });
 
 
-    describe('Variant #15: export all declaration source literal', () => {
+    describe('Variant #16: export all declaration source literal', () => {
         const exportAllDeclarationRegExp: RegExp = /export *\* *from *'foo';/;
         const exportAllDeclarationRegExp: RegExp = /export *\* *from *'foo';/;
 
 
         let obfuscatedCode: string;
         let obfuscatedCode: string;
@@ -354,7 +426,7 @@ describe('SplitStringTransformer', () => {
         });
         });
     });
     });
 
 
-    describe('Variant #16: export named declaration source literal', () => {
+    describe('Variant #17: export named declaration source literal', () => {
         const exportNamedDeclarationRegExp: RegExp = /export *{ *bar *} *from *'foo';/;
         const exportNamedDeclarationRegExp: RegExp = /export *{ *bar *} *from *'foo';/;
 
 
         let obfuscatedCode: string;
         let obfuscatedCode: string;

+ 51 - 0
test/functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec.ts

@@ -438,6 +438,57 @@ describe('DeadCodeInjectionTransformer', () => {
                     assert.equal(superExpressionMatchesLength, expectedSuperExpressionMatchesLength);
                     assert.equal(superExpressionMatchesLength, expectedSuperExpressionMatchesLength);
                 });
                 });
             });
             });
+
+            describe('Variant #6 - for-await expression in block statement', () => {
+                const functionRegExp: RegExp = new RegExp(
+                    `var ${variableMatch} *= *function *\\(\\) *\\{` +
+                        `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
+                    `\\};`,
+                    'g'
+                );
+                const awaitExpressionRegExp: RegExp = new RegExp(
+                    `for await *\\(const ${variableMatch} of *\\[]\\){}`,
+                    'g'
+                );
+                const expectedFunctionMatchesLength: number = 4;
+                const expectedAwaitExpressionMatchesLength: number = 1;
+
+                let functionMatchesLength: number = 0,
+                    awaitExpressionMatchesLength: number = 0;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/for-await-expression.js');
+
+                    const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            deadCodeInjection: true,
+                            deadCodeInjectionThreshold: 1,
+                            stringArray: true,
+                            stringArrayThreshold: 1
+                        }
+                    ).getObfuscatedCode();
+                    const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
+                    const awaitExpressionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(awaitExpressionRegExp);
+
+                    if (functionMatches) {
+                        functionMatchesLength = functionMatches.length;
+                    }
+
+                    if (awaitExpressionMatches) {
+                        awaitExpressionMatchesLength = awaitExpressionMatches.length;
+                    }
+                });
+
+                it('match #1: shouldn\'t add dead code', () => {
+                    assert.equal(functionMatchesLength, expectedFunctionMatchesLength);
+                });
+
+                it('match #2: shouldn\'t add dead code', () => {
+                    assert.equal(awaitExpressionMatchesLength, expectedAwaitExpressionMatchesLength);
+                });
+            });
         });
         });
 
 
         describe('Variant #5 - chance of `IfStatement` variant', () => {
         describe('Variant #5 - chance of `IfStatement` variant', () => {

+ 25 - 0
test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/for-await-expression.js

@@ -0,0 +1,25 @@
+(async function(){
+    if (true) {
+        var foo = function () {
+            console.log('abc');
+        };
+        var bar = function () {
+            console.log('def');
+        };
+        var baz = function () {
+            console.log('ghi');
+        };
+        var bark = function () {
+            console.log('jkl');
+        };
+
+        if (true) {
+            for await (const item of []) {}
+        }
+
+        foo();
+        bar();
+        baz();
+        bark();
+    }
+})();

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

@@ -0,0 +1,206 @@
+import { assert } from 'chai';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+
+import { IdentifierNamesGenerator } from '../../../../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
+
+import { readFileAsString } from '../../../../helpers/readFileAsString';
+
+import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
+
+describe('EscapeSequenceTransformer', function () {
+    this.timeout(120000);
+
+    describe('Variant #1: string contains non-latin and non-digit characters and `unicodeEscapeSequence` is disabled', () => {
+        let testFunc: () => void;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/error-when-non-latin.js');
+
+            testFunc = () => JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    stringArray: true,
+                    stringArrayThreshold: 1
+                }
+            );
+        });
+
+        it('should\'t throw an error', () => {
+            assert.doesNotThrow(testFunc);
+        });
+    });
+
+    describe('Variant #2: `unicodeEscapeSequence` option is enabled', () => {
+        const regExp: RegExp = /^var test *= *'\\x74\\x65\\x73\\x74';$/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    unicodeEscapeSequence: true
+
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('should replace literal node value with unicode escape sequence', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
+    });
+
+    describe('Variant #3: `unicodeEscapeSequence` and `stringArray` options are enabled', () => {
+        const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['\\x74\\x65\\x73\\x74'\];/;
+        const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('\\x30\\x78\\x30'\);/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    stringArray: true,
+                    stringArrayThreshold: 1,
+                    unicodeEscapeSequence: true
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('match #1: should replace literal node value with unicode escape sequence from string array', () => {
+            assert.match(obfuscatedCode, stringArrayRegExp);
+        });
+
+        it('match #2: should replace literal node value with unicode escape sequence from string array', () => {
+            assert.match(obfuscatedCode, stringArrayCallRegExp);
+        });
+    });
+
+    describe('Variant #4: `reservedStrings` option is enabled', () => {
+        describe('Variant #1: base', () => {
+            const stringLiteralRegExp1: RegExp = /const foo *= *'foo';/;
+            const stringLiteralRegExp2: RegExp = /const bar *= *'\\x62\\x61\\x72';/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/reserved-strings-option-1.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        reservedStrings: ['foo'],
+                        unicodeEscapeSequence: true
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('match #1: should ignore reserved strings', () => {
+                assert.match(obfuscatedCode, stringLiteralRegExp1);
+            });
+
+            it('match #2: should transform non-reserved strings', () => {
+                assert.match(obfuscatedCode, stringLiteralRegExp2);
+            });
+        });
+
+        describe('Variant #2: correct escape of special characters', () => {
+            const stringLiteralRegExp: RegExp = /var baz *= *'Cannot find module \\'' *\+ *foo *\+ *'\\x27';/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/reserved-strings-option-2.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        reservedStrings: ['a']
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('match #1: should ignore reserved strings', () => {
+                assert.match(obfuscatedCode, stringLiteralRegExp);
+            });
+        });
+    });
+
+    describe('Variant #5: `forceTransformStrings` option is enabled', () => {
+        const stringLiteralRegExp1: RegExp = /const foo *= *'foo';/;
+        const stringLiteralRegExp2: RegExp = /const bar *= *'bar';/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/force-transform-strings-option.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    forceTransformStrings: ['bar'],
+                    unicodeEscapeSequence: false
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('match #1: should not encode force transform string with unicode escape sequence', () => {
+            assert.match(obfuscatedCode, stringLiteralRegExp1);
+        });
+
+        it('match #2: should not encode force transform string with unicode escape sequence', () => {
+            assert.match(obfuscatedCode, stringLiteralRegExp2);
+        });
+    });
+
+    describe('Variant #6: `stringArrayWrappersCount` option enabled', () => {
+        const stringArrayCallRegExp: RegExp = new RegExp(
+                'return e;' +
+            '};' +
+            'const f *= *b;' +
+            'const foo *= *f\\(\'\\\\x30\\\\x78\\\\x30\'\\);' +
+            'const bar *= *f\\(\'\\\\x30\\\\x78\\\\x31\'\\);' +
+            'const baz *= *f\\(\'\\\\x30\\\\x78\\\\x32\'\\);' +
+            'function test\\( *\\) *{' +
+                'const g *= *f;' +
+                'const c *= *g\\(\'\\\\x30\\\\x78\\\\x33\'\\);' +
+                'const d *= *g\\(\'\\\\x30\\\\x78\\\\x34\'\\);' +
+                'const e *= *g\\(\'\\\\x30\\\\x78\\\\x35\'\\);' +
+            '}'
+        );
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/wrappers-count.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
+                    stringArray: true,
+                    stringArrayThreshold: 1,
+                    stringArrayWrappersChainedCalls: true,
+                    stringArrayWrappersCount: 1,
+                    unicodeEscapeSequence: true
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('should encode calls to the string array wrappers', () => {
+            assert.match(obfuscatedCode, stringArrayCallRegExp);
+        });
+    });
+});

+ 0 - 0
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/error-when-non-latin.js → test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/error-when-non-latin.js


+ 0 - 0
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/reserved-strings-option-1.js → test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/force-transform-strings-option.js


+ 2 - 0
test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/reserved-strings-option-1.js

@@ -0,0 +1,2 @@
+const foo = 'foo';
+const bar = 'bar';

+ 0 - 0
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/reserved-strings-option-2.js → test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/reserved-strings-option-2.js


+ 1 - 0
test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/simple-input.js

@@ -0,0 +1 @@
+var test = 'test';

+ 9 - 0
test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/wrappers-count.js

@@ -0,0 +1,9 @@
+const foo = 'foo'
+const bar = 'bar';
+const baz = 'baz';
+
+function test () {
+    const bark = 'bark'
+    const hawk = 'hawk';
+    const eagle = 'eagle';
+}

+ 8 - 6
test/functional-tests/node-transformers/initializing-transformers/comments-transformer/CommentsTransformer.spec.ts

@@ -7,6 +7,8 @@ import { readFileAsString } from '../../../../helpers/readFileAsString';
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
 
 
 describe('CommentsTransformer', () => {
 describe('CommentsTransformer', () => {
+    const lineSeparatorEscaped: string = '\\r?\\n';
+
     describe('Variant #1: simple comment without preserved words', () => {
     describe('Variant #1: simple comment without preserved words', () => {
         const regExp: RegExp = /^var test *= *0x1;$/;
         const regExp: RegExp = /^var test *= *0x1;$/;
 
 
@@ -72,10 +74,10 @@ describe('CommentsTransformer', () => {
 
 
     describe('Variant #4: comment with preserved and non-preserved words', () => {
     describe('Variant #4: comment with preserved and non-preserved words', () => {
         const regExp: RegExp = new RegExp(``+
         const regExp: RegExp = new RegExp(``+
-            `^\\/\\*\\* *\\n` +
-            ` *\\* *@license *\\n` +
-            ` *\\* *test\\n` +
-            ` *\\*\\/\\n` +
+            `^\\/\\*\\* *${lineSeparatorEscaped}` +
+            ` *\\* *@license *${lineSeparatorEscaped}` +
+            ` *\\* *test${lineSeparatorEscaped}` +
+            ` *\\*\\/${lineSeparatorEscaped}` +
             `var test *= *0x1;` +
             `var test *= *0x1;` +
             ` *\\/\\*\\* *@preserved *\\*\\/$` +
             ` *\\/\\*\\* *@preserved *\\*\\/$` +
         ``);
         ``);
@@ -117,7 +119,7 @@ describe('CommentsTransformer', () => {
         });
         });
     });
     });
 
 
-    describe('Variant #5: only comment with preserved words', () => {
+    describe('Variant #6: only comment with preserved words', () => {
         const regExp: RegExp = /^\/\/ *@license$/;
         const regExp: RegExp = /^\/\/ *@license$/;
 
 
         let obfuscatedCode: string;
         let obfuscatedCode: string;
@@ -138,7 +140,7 @@ describe('CommentsTransformer', () => {
         });
         });
     });
     });
 
 
-    describe('Variant #6: simple comment with preserved words and additional code helper is inserted', () => {
+    describe('Variant #7: simple comment with preserved words and additional code helper is inserted', () => {
         describe('Variant #1: `stringArray` code helper', () => {
         describe('Variant #1: `stringArray` code helper', () => {
             const regExp: RegExp = /^\/\/ *@license *test *comment *\n*var _0x([a-f0-9]){4} *= *\['abc'];/;
             const regExp: RegExp = /^\/\/ *@license *test *comment *\n*var _0x([a-f0-9]){4} *= *\['abc'];/;
 
 

+ 69 - 0
test/functional-tests/node-transformers/preparing-transformers/obfuscating-guards/force-transform-string-obfuscating-guard/ForceTransformStringObfuscatingGuard.spec.ts

@@ -0,0 +1,69 @@
+import { assert } from 'chai';
+
+import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscatorFacade';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
+
+import { IdentifierNamesGenerator } from '../../../../../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
+
+import { readFileAsString } from '../../../../../helpers/readFileAsString';
+
+describe('ForceTransformStringObfuscatingGuard', () => {
+    describe('check', () => {
+        describe('`forceTransformStrings` option is enabled', () => {
+            const obfuscatingGuardRegExp: RegExp = new RegExp(
+                'var foo *= *\'foo\';' +
+                'var bar *= *b\\(\'0x0\'\\);'
+            );
+
+            let obfuscatedCode: string;
+
+            beforeEach(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/base-behaviour.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        forceTransformStrings: ['bar'],
+                        identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
+                        stringArray: true,
+                        stringArrayThreshold: 0
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('match #1: should obfuscate force transform strings', () => {
+                assert.match(obfuscatedCode, obfuscatingGuardRegExp);
+            });
+        });
+
+        describe('`forceTransformStrings` option is disabled', () => {
+            const obfuscatingGuardRegExp: RegExp = new RegExp(
+                'var foo *= *\'foo\';' +
+                'var bar *= *\'bar\';'
+            );
+
+            let obfuscatedCode: string;
+
+            beforeEach(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/base-behaviour.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        forceTransformStrings: [],
+                        identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
+                        stringArray: true,
+                        stringArrayThreshold: 0
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('match #1: shouldn\'t obfuscate strings', () => {
+                assert.match(obfuscatedCode, obfuscatingGuardRegExp);
+            });
+        });
+    });
+});

+ 2 - 0
test/functional-tests/node-transformers/preparing-transformers/obfuscating-guards/force-transform-string-obfuscating-guard/fixtures/base-behaviour.js

@@ -0,0 +1,2 @@
+var foo = 'foo';
+var bar = 'bar';

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

@@ -49,7 +49,7 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
 
 
             describe('Variant #2: option value is bigger then count `literal` nodes in the scope', () => {
             describe('Variant #2: option value is bigger then count `literal` nodes in the scope', () => {
                 const stringArrayCallRegExp: RegExp = new RegExp(
                 const stringArrayCallRegExp: RegExp = new RegExp(
-                    'return _0x([a-f0-9]){4,6};' +
+                        'return _0x([a-f0-9]){4,6};' +
                     '};' +
                     '};' +
                     'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
                     'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
                     'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
                     'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
@@ -116,11 +116,11 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
             describe('Variant #1: option value is lower then count `literal` nodes in the scope', () => {
             describe('Variant #1: option value is lower then count `literal` nodes in the scope', () => {
                 const stringArrayCallRegExp: RegExp = new RegExp(
                 const stringArrayCallRegExp: RegExp = new RegExp(
                     'function test *\\( *\\) *{' +
                     'function test *\\( *\\) *{' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x3\'\\);' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x4\'\\);' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x5\'\\);' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x3\'\\);' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x4\'\\);' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x5\'\\);' +
                     '}'
                     '}'
                 );
                 );
 
 
@@ -148,12 +148,12 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
             describe('Variant #2: option value is bigger then count `literal` nodes in the scope', () => {
             describe('Variant #2: option value is bigger then count `literal` nodes in the scope', () => {
                 const stringArrayCallRegExp: RegExp = new RegExp(
                 const stringArrayCallRegExp: RegExp = new RegExp(
                     'function test *\\(\\) *{' +
                     'function test *\\(\\) *{' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x3\'\\);' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x4\'\\);' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x5\'\\);' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x3\'\\);' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x4\'\\);' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x5\'\\);' +
                     '}'
                     '}'
                 );
                 );
 
 
@@ -181,11 +181,11 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
             describe('Variant #3: correct wrappers order', () => {
             describe('Variant #3: correct wrappers order', () => {
                 const stringArrayCallRegExp: RegExp = new RegExp(
                 const stringArrayCallRegExp: RegExp = new RegExp(
                     'function test *\\( *\\) *{' +
                     'function test *\\( *\\) *{' +
-                    'const h *= *b;' +
-                    'const i *= *b;' +
-                    'const c *= *[h|i]\\(\'0x3\'\\);' +
-                    'const d *= *[h|i]\\(\'0x4\'\\);' +
-                    'const e *= *[h|i]\\(\'0x5\'\\);' +
+                        'const h *= *b;' +
+                        'const i *= *b;' +
+                        'const c *= *[h|i]\\(\'0x3\'\\);' +
+                        'const d *= *[h|i]\\(\'0x4\'\\);' +
+                        'const e *= *[h|i]\\(\'0x5\'\\);' +
                     '}'
                     '}'
                 );
                 );
 
 
@@ -249,7 +249,7 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
                 const stringArrayCallRegExp: RegExp = new RegExp(
                 const stringArrayCallRegExp: RegExp = new RegExp(
                     'var c *= *b;' +
                     'var c *= *b;' +
                     'if *\\(!!\\[]\\) *{' +
                     'if *\\(!!\\[]\\) *{' +
-                    'var foo *= *c\\(\'0x0\'\\);' +
+                        'var foo *= *c\\(\'0x0\'\\);' +
                     '}'
                     '}'
                 );
                 );
 
 
@@ -306,7 +306,7 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
 
 
         describe('Variant #4: prevailing kind of variables', () => {
         describe('Variant #4: prevailing kind of variables', () => {
             const stringArrayCallRegExp: RegExp = new RegExp(
             const stringArrayCallRegExp: RegExp = new RegExp(
-                'return _0x([a-f0-9]){4,6};' +
+                    'return _0x([a-f0-9]){4,6};' +
                 '};' +
                 '};' +
                 'var _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
                 'var _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
                 'var _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
                 'var _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
@@ -368,22 +368,22 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
                         'const q *= *b;' +
                         'const q *= *b;' +
                         'const foo *= *q\\(\'0x0\'\\);' +
                         'const foo *= *q\\(\'0x0\'\\);' +
                         'function test\\(c, *d\\) *{' +
                         'function test\\(c, *d\\) *{' +
-                        'const r *= *q;' +
-                        'const e *= *r\\(\'0x1\'\\);' +
-                        'const f *= *r\\(\'0x2\'\\);' +
-                        'function g\\(h, *i\\) *{' +
-                        'const s *= *r;' +
-                        'const j *= *s\\(\'0x3\'\\);' +
-                        'const k *= *s\\(\'0x4\'\\);' +
-                        'function l\\(m, *n *\\) *{' +
-                        'const t *= *s;' +
-                        'const o *= *t\\(\'0x3\'\\);' +
-                        'const p *= *t\\(\'0x4\'\\);' +
-                        'return o *\\+ *p;' +
-                        '}' +
-                        'return j *\\+ *k;' +
-                        '}' +
-                        'return e *\\+ *f *\\+ *g\\(\\);' +
+                            'const r *= *q;' +
+                            'const e *= *r\\(\'0x1\'\\);' +
+                            'const f *= *r\\(\'0x2\'\\);' +
+                            'function g\\(h, *i\\) *{' +
+                                'const s *= *r;' +
+                                'const j *= *s\\(\'0x3\'\\);' +
+                                'const k *= *s\\(\'0x4\'\\);' +
+                                'function l\\(m, *n *\\) *{' +
+                                    'const t *= *s;' +
+                                    'const o *= *t\\(\'0x3\'\\);' +
+                                    'const p *= *t\\(\'0x4\'\\);' +
+                                    'return o *\\+ *p;' +
+                                '}' +
+                                'return j *\\+ *k;' +
+                            '}' +
+                            'return e *\\+ *f *\\+ *g\\(\\);' +
                         '}'
                         '}'
                     );
                     );
 
 
@@ -869,7 +869,7 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
         describe('Variant #1: root scope', () => {
         describe('Variant #1: root scope', () => {
             describe('Variant #1: `1` scope calls wrapper for each encoding type', () => {
             describe('Variant #1: `1` scope calls wrapper for each encoding type', () => {
                 const stringArrayWrappersRegExp: RegExp = new RegExp(
                 const stringArrayWrappersRegExp: RegExp = new RegExp(
-                    'return _0x([a-f0-9]){4,6};' +
+                        'return _0x([a-f0-9]){4,6};' +
                     '};' +
                     '};' +
                     'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
                     'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
                     // this one may be added or not depends on:
                     // this one may be added or not depends on:
@@ -907,7 +907,7 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
 
 
             describe('Variant #2: `2` scope calls wrappers for each encoding type', () => {
             describe('Variant #2: `2` scope calls wrappers for each encoding type', () => {
                 const stringArrayWrappersRegExp: RegExp = new RegExp(
                 const stringArrayWrappersRegExp: RegExp = new RegExp(
-                    'return _0x([a-f0-9]){4,6};' +
+                        'return _0x([a-f0-9]){4,6};' +
                     '};' +
                     '};' +
                     'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
                     'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
                     'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
                     'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
@@ -949,13 +949,13 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
             describe('Variant #1: `1` scope calls wrapper for each encoding type', () => {
             describe('Variant #1: `1` scope calls wrapper for each encoding type', () => {
                 const stringArrayWrappersRegExp: RegExp = new RegExp(
                 const stringArrayWrappersRegExp: RegExp = new RegExp(
                     'function test *\\( *\\) *{' +
                     'function test *\\( *\\) *{' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
-                    // this one may be added or not depends on:
-                    // if all literal values encoded with a single encoding or not
-                    '(?:const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};)?' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x3\'\\);' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x4\'\\);' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x5\'\\);' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
+                        // this one may be added or not depends on:
+                        // if all literal values encoded with a single encoding or not
+                        '(?:const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};)?' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x3\'\\);' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x4\'\\);' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x5\'\\);' +
                     '}'
                     '}'
                 );
                 );
 
 
@@ -987,14 +987,14 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
             describe('Variant #2: `2` scope calls wrappers for each encoding type', () => {
             describe('Variant #2: `2` scope calls wrappers for each encoding type', () => {
                 const stringArrayWrappersRegExp: RegExp = new RegExp(
                 const stringArrayWrappersRegExp: RegExp = new RegExp(
                     'function test *\\( *\\) *{' +
                     'function test *\\( *\\) *{' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
-                    // this one may be added or not depends on:
-                    // if all literal values encoded with a single encoding or not
-                    '(?:const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};)?' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x3\'\\);' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x4\'\\);' +
-                    'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x5\'\\);' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};' +
+                        // this one may be added or not depends on:
+                        // if all literal values encoded with a single encoding or not
+                        '(?:const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4};)?' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x3\'\\);' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x4\'\\);' +
+                        'const _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\\(\'0x5\'\\);' +
                     '}'
                     '}'
                 );
                 );
 
 

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