Browse Source

Sync with dev

sanex 4 năm trước cách đây
mục cha
commit
b463f774c8
100 tập tin đã thay đổi với 1191 bổ sung478 xóa
  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",
         "indent": "off",
         "jsdoc/no-types": "off",
-        "linebreak-style": "error",
+        "linebreak-style": "off",
         "max-classes-per-file": [
             "error",
             1
@@ -327,7 +327,6 @@ module.exports = {
                 "name": "error"
             }
         ],
-        "unicorn/no-array-instanceof": "error",
         "unicorn/no-nested-ternary": "error",
         "unicorn/no-unreadable-array-destructuring": "error",
         "unicorn/prefer-includes": "error",

+ 1 - 1
.gitignore

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

+ 0 - 1
.npmignore

@@ -7,4 +7,3 @@ examples
 images
 webpack
 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
 
-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:
   yarn: true
@@ -16,5 +24,4 @@ cache:
 script: "yarn run travis"
 
 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
 
+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
 ---
 * **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)
 [![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)
-[![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) 
 [![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)
@@ -307,7 +307,7 @@ Kind of variables of inserted nodes will auto-detected, based on most prevailing
 ## 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.
-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.
 
@@ -339,6 +339,7 @@ Following options are available for the JS Obfuscator:
     debugProtectionInterval: false,
     disableConsoleOutput: false,
     domainLock: [],
+    forceTransformStrings: [],
     identifierNamesGenerator: 'hexadecimal',
     identifiersDictionary: [],
     identifiersPrefix: '',
@@ -391,6 +392,7 @@ Following options are available for the JS Obfuscator:
     --disable-console-output <boolean>
     --domain-lock '<list>' (comma separated)
     --exclude '<list>' (comma separated)
+    --force-transform-strings '<list>' (comma separated)
     --identifier-names-generator <string> [dictionary, hexadecimal, mangled, mangled-shuffled]
     --identifiers-dictionary '<list>' (comma separated)
     --identifiers-prefix <string>
@@ -423,6 +425,8 @@ Following options are available for the JS Obfuscator:
     --unicode-escape-sequence <boolean>
 ```
 
+<!-- ##options-start## -->
+
 ### `compact`
 Type: `boolean` Default: `true`
 
@@ -656,13 +660,32 @@ Type: `string[]` Default: `[]`
 
 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`
 Type: `string` Default: `hexadecimal`
 
 Sets identifier names generator.
 
 Available values:
-* `dictionary`: identifier names from [`identifiersDictionary`](#identifiersDictionary) list
+* `dictionary`: identifier names from [`identifiersDictionary`](#identifiersdictionary) list
 * `hexadecimal`: identifier names like `_0xabc123`
 * `mangled`: short identifier names like `a`, `b`, `c`
 * `mangled-shuffled`: same as `mangled` but with shuffled alphabet
@@ -670,7 +693,7 @@ Available values:
 ### `identifiersDictionary`
 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`
 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.
 
-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: 
 ```ts
@@ -906,7 +929,7 @@ Specifies source map generation mode:
 ### `splitStrings`
 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:
 ```ts
@@ -924,7 +947,7 @@ Example:
 ### `splitStringsChunkLength`
 Type: `number` Default: `10`
 
-Sets chunk length of [`splitStrings`](#splitStrings) option.
+Sets chunk length of [`splitStrings`](#splitstrings) option.
 
 ### `stringArray`
 Type: `boolean` Default: `true`
@@ -1012,7 +1035,7 @@ const eagle = _0x26ca42('0x5');
 ### `stringArrayWrappersChainedCalls`
 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.
 
@@ -1067,7 +1090,7 @@ function test() {
 ### `stringArrayWrappersType`
 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.
 
@@ -1310,6 +1333,8 @@ Performance will slightly slower than without obfuscation
 }
 ```
 
+<!-- ##options-end## -->
+
 ## Frequently Asked Questions
 
 ### What javascript versions are supported?

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/index.browser.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/index.cli.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/index.js


+ 38 - 37
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "2.3.0",
+  "version": "2.5.0",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -23,13 +23,13 @@
   "dependencies": {
     "@gradecam/tsenum": "1.2.0",
     "@nuxtjs/opencollective": "0.2.2",
-    "acorn": "8.0.1",
+    "acorn": "8.0.3",
     "chalk": "4.1.0",
     "chance": "1.1.7",
     "class-validator": "0.12.2",
     "commander": "6.1.0",
     "escodegen": "2.0.0",
-    "eslint-scope": "^5.1.1",
+    "eslint-scope": "5.1.1",
     "estraverse": "5.2.0",
     "eventemitter3": "4.0.7",
     "fast-deep-equal": "3.1.3",
@@ -41,35 +41,39 @@
     "reflect-metadata": "0.1.13",
     "source-map-support": "0.5.19",
     "string-template": "1.0.0",
-    "tslib": "2.0.1"
+    "stringz": "2.1.0",
+    "tslib": "2.0.3"
   },
   "devDependencies": {
-    "@types/chai": "4.2.12",
+    "@istanbuljs/nyc-config-typescript": "1.0.1",
+    "@types/chai": "4.2.13",
     "@types/chance": "1.1.0",
     "@types/escodegen": "0.0.6",
     "@types/eslint-scope": "3.7.0",
     "@types/estraverse": "5.1.0",
     "@types/estree": "0.0.45",
+    "@types/js-string-escape": "1.0.0",
     "@types/md5": "2.2.0",
     "@types/mkdirp": "1.0.1",
     "@types/mocha": "8.0.3",
     "@types/multimatch": "4.0.0",
-    "@types/node": "14.11.1",
+    "@types/node": "14.11.8",
     "@types/rimraf": "3.0.0",
-    "@types/sinon": "9.0.5",
+    "@types/sinon": "9.0.8",
     "@types/string-template": "1.0.2",
     "@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-exclude": "2.0.2",
     "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-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-webpack-plugin": "5.2.0",
     "mocha": "8.1.3",
@@ -77,13 +81,13 @@
     "pjson": "1.0.9",
     "pre-commit": "1.2.2",
     "rimraf": "3.0.2",
-    "sinon": "9.0.3",
+    "sinon": "9.2.0",
     "threads": "1.6.3",
-    "ts-loader": "8.0.3",
+    "ts-loader": "8.0.4",
     "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"
   },
   "repository": {
@@ -92,25 +96,22 @@
   },
   "homepage": "https://obfuscator.io/",
   "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"
   },
   "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.CustomCodeHelpersTransformer,
         NodeTransformer.DeadCodeInjectionTransformer,
+        NodeTransformer.EscapeSequenceTransformer,
         NodeTransformer.EvalCallExpressionTransformer,
         NodeTransformer.ExpressionStatementsMergeTransformer,
         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 { NodeGuards } from '../../node/NodeGuards';
-import { NodeMetadata } from '../../node/NodeMetadata';
 import { NodeLiteralUtils } from '../../node/NodeLiteralUtils';
+import { NodeMetadata } from '../../node/NodeMetadata';
 
 /**
  * Adds values of literal nodes to the string array storage
@@ -99,7 +99,7 @@ export class StringArrayStorageAnalyzer implements IStringArrayStorageAnalyzer {
      * @param {Node} parentNode
      */
     private analyzeLiteralNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): void {
-        if (typeof literalNode.value !== 'string') {
+        if (!NodeLiteralUtils.isStringLiteralNode(literalNode)) {
             return;
         }
 
@@ -107,7 +107,7 @@ export class StringArrayStorageAnalyzer implements IStringArrayStorageAnalyzer {
             return;
         }
 
-        if (!this.shouldAddValueToStringArray(literalNode.value)) {
+        if (!this.shouldAddValueToStringArray(literalNode)) {
             return;
         }
 
@@ -118,11 +118,17 @@ export class StringArrayStorageAnalyzer implements IStringArrayStorageAnalyzer {
     }
 
     /**
-     * @param {string} value
+     * @param {(SimpleLiteral & {value: string})} literalNode
      * @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;
     }
 }

+ 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',
                 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(
                 '--identifier-names-generator <string>',
                 'Sets identifier names generator. ' +

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

@@ -27,7 +27,7 @@ export class ObfuscatedCodeWriter {
         inputPath: string,
         options: TInputCLIOptions
     ) {
-        this.inputPath = inputPath;
+        this.inputPath = path.normalize(inputPath);
         this.options = options;
     }
 
@@ -36,13 +36,13 @@ export class ObfuscatedCodeWriter {
      * @returns {string}
      */
     public getOutputCodePath (filePath: string): string {
+        const normalizedFilePath: string = path.normalize(filePath);
         const normalizedRawOutputPath: string | null = this.options.output
             ? path.normalize(this.options.output)
             : null;
 
         if (!normalizedRawOutputPath) {
-            return path
-                .normalize(filePath)
+            return normalizedFilePath
                 .split(StringSeparator.Dot)
                 .map((value: string, index: number) => {
                     return index === 0 ? `${value}${JavaScriptObfuscatorCLI.obfuscatedFilePrefix}` : value;
@@ -60,7 +60,9 @@ export class ObfuscatedCodeWriter {
 
         if (isDirectoryRawInputPath) {
             if (isDirectoryRawOutputPath) {
-                return path.join(normalizedRawOutputPath, filePath);
+                const baseOutputPath: string = normalizedFilePath.replace(this.inputPath, '');
+
+                return path.join(normalizedRawOutputPath, baseOutputPath);
             } else {
                 throw new Error('Output path for directory obfuscation should be a directory path');
             }
@@ -79,19 +81,38 @@ export class ObfuscatedCodeWriter {
      * @returns {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) {
-            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[] {
         fs.readdirSync(directoryPath, JavaScriptObfuscatorCLI.encoding)
             .forEach((fileName: string) => {
-                const filePath: string = `${directoryPath}/${fileName}`;
+                const filePath: string = path.join(directoryPath, fileName);
 
                 if (
                     SourceCodeReader.isDirectoryPath(filePath)

+ 1 - 1
src/constants/EcmaVersion.ts

@@ -1,3 +1,3 @@
 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__IObjectExpressionKeysTransformerCustomNode = 'Factory<IObjectExpressionKeysTransformerCustomNode>',
     Factory__IObjectExpressionExtractor = 'Factory<IObjectExpressionExtractor>',
-    Factory__IStringArrayTransformerCustomNode = 'Factory<IStringArrayTransformerCustomNode>',
+    Factory__IStringArrayCustomNode = 'Factory<IStringArrayCustomNode>',
     Factory__TControlFlowStorage = 'Factory<TControlFlowStorage>',
     IArrayUtils = 'IArrayUtils',
     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 { DeadCodeInjectionCustomNode } from '../../../enums/custom-nodes/DeadCodeInjectionCustomNode';
 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 { BinaryExpressionFunctionNode } from '../../../custom-nodes/control-flow-flattening-nodes/BinaryExpressionFunctionNode';
@@ -72,18 +72,18 @@ export const customNodesModule: interfaces.ContainerModule = new ContainerModule
         .toConstructor(ObjectExpressionVariableDeclarationHostNode)
         .whenTargetNamed(ObjectExpressionKeysTransformerCustomNode.ObjectExpressionVariableDeclarationHostNode);
 
-    // string array transformer nodes
+    // string array nodes
     bind<interfaces.Newable<ICustomNode>>(ServiceIdentifiers.Newable__ICustomNode)
         .toConstructor(StringArrayCallNode)
-        .whenTargetNamed(StringArrayTransformerCustomNode.StringArrayCallNode);
+        .whenTargetNamed(StringArrayCustomNode.StringArrayCallNode);
 
     bind<interfaces.Newable<ICustomNode>>(ServiceIdentifiers.Newable__ICustomNode)
         .toConstructor(StringArrayScopeCallsWrapperFunctionNode)
-        .whenTargetNamed(StringArrayTransformerCustomNode.StringArrayScopeCallsWrapperFunctionNode);
+        .whenTargetNamed(StringArrayCustomNode.StringArrayScopeCallsWrapperFunctionNode);
 
     bind<interfaces.Newable<ICustomNode>>(ServiceIdentifiers.Newable__ICustomNode)
         .toConstructor(StringArrayScopeCallsWrapperVariableNode)
-        .whenTargetNamed(StringArrayTransformerCustomNode.StringArrayScopeCallsWrapperVariableNode);
+        .whenTargetNamed(StringArrayCustomNode.StringArrayScopeCallsWrapperVariableNode);
 
     // control flow customNode constructor factory
     bind<ICustomNode>(ServiceIdentifiers.Factory__IControlFlowCustomNode)
@@ -118,10 +118,10 @@ export const customNodesModule: interfaces.ContainerModule = new ContainerModule
                 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
-            .getConstructorFactory<StringArrayTransformerCustomNode, ICustomNode>(
+            .getConstructorFactory<StringArrayCustomNode, ICustomNode>(
                 ServiceIdentifiers.Newable__ICustomNode,
                 ServiceIdentifiers.Factory__IIdentifierNamesGenerator,
                 ServiceIdentifiers.ICustomCodeHelperFormatter,

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

@@ -1,5 +1,15 @@
 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) => {
     // 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 { CustomCodeHelpersTransformer } from '../../../node-transformers/preparing-transformers/CustomCodeHelpersTransformer';
 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 { ObfuscatingGuardsTransformer } from '../../../node-transformers/preparing-transformers/ObfuscatingGuardsTransformer';
 import { ParentificationTransformer } from '../../../node-transformers/preparing-transformers/ParentificationTransformer';
@@ -40,6 +41,10 @@ export const preparingTransformersModule: interfaces.ContainerModule = new Conta
         .to(ParentificationTransformer)
         .whenTargetNamed(NodeTransformer.ParentificationTransformer);
 
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(VariablePreserveTransformer)
+        .whenTargetNamed(NodeTransformer.VariablePreserveTransformer);
+
     // obfuscating guards
     bind<IObfuscatingGuard>(ServiceIdentifiers.INodeGuard)
         .to(BlackListObfuscatingGuard)
@@ -51,15 +56,16 @@ export const preparingTransformersModule: interfaces.ContainerModule = new Conta
         .inSingletonScope()
         .whenTargetNamed(ObfuscatingGuard.ConditionalCommentObfuscatingGuard);
 
+    bind<IObfuscatingGuard>(ServiceIdentifiers.INodeGuard)
+        .to(ForceTransformStringObfuscatingGuard)
+        .inSingletonScope()
+        .whenTargetNamed(ObfuscatingGuard.ForceTransformStringObfuscatingGuard);
+
     bind<IObfuscatingGuard>(ServiceIdentifiers.INodeGuard)
         .to(ReservedStringObfuscatingGuard)
         .inSingletonScope()
         .whenTargetNamed(ObfuscatingGuard.ReservedStringObfuscatingGuard);
 
-    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
-        .to(VariablePreserveTransformer)
-        .whenTargetNamed(NodeTransformer.VariablePreserveTransformer);
-
     // obfuscating guards factory
     bind<IObfuscatingGuard>(ServiceIdentifiers.Factory__INodeGuard)
         .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';
 
 export const stringArrayTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
-    // string array transformers
+    // strings transformers
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
         .to(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 { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IStringArrayStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayStorage';
+import { IStringArrayStorageItemData } from '../../interfaces/storages/string-array-transformers/IStringArrayStorageItem';
 
 import { initializable } from '../../decorators/Initializable';
 
@@ -16,6 +17,7 @@ import { StringArrayTemplate } from './templates/string-array/StringArrayTemplat
 
 import { AbstractCustomCodeHelper } from '../AbstractCustomCodeHelper';
 import { NodeUtils } from '../../node/NodeUtils';
+import { StringUtils } from '../../utils/StringUtils';
 
 @injectable()
 export class StringArrayCodeHelper extends AbstractCustomCodeHelper {
@@ -81,7 +83,21 @@ export class StringArrayCodeHelper extends AbstractCustomCodeHelper {
     protected getCodeHelperTemplate (): string {
         return this.customCodeHelperFormatter.formatTemplate(StringArrayTemplate(), {
             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 {
     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()
     private stringArrayCallsWrapperName!: string;
 
-
     /**
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
      * @param {ICustomCodeHelperFormatter} customCodeHelperFormatter

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

@@ -6,6 +6,7 @@ import * as eslintScope from 'eslint-scope';
 
 declare module 'estree' {
     export interface BaseNodeMetadata {
+        forceTransformNode?: 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',
     StringArrayScopeCallsWrapperFunctionNode = 'StringArrayScopeCallsWrapperFunctionNode',
     StringArrayScopeCallsWrapperVariableNode = 'StringArrayScopeCallsWrapperVariableNode'

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

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

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

@@ -1,5 +1,6 @@
 export enum ObfuscatingGuard {
     BlackListObfuscatingGuard = 'BlackListObfuscatingGuard',
     ConditionalCommentObfuscatingGuard = 'ConditionalCommentObfuscatingGuard',
+    ForceTransformStringObfuscatingGuard = 'ForceTransformStringObfuscatingGuard',
     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
      * @returns {boolean}
      */
+    // eslint-disable-next-line complexity
     public static isIncrementedMangledName (nextName: string, prevName: string): boolean {
         if (nextName === prevName) {
             return false;
@@ -76,6 +77,8 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
             return nextNameLength > prevNameLength;
         }
 
+        let isIncrementedPrevCharacter: boolean = false;
+
         for (let i: number = 0; i < nextNameLength; i++) {
             const nextNameCharacter: string = nextName[i];
             const prevNameCharacter: string = prevName[i];
@@ -84,6 +87,17 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
                 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 isUpperCasePrevNameCharacter: boolean = MangledIdentifierNamesGenerator.isUpperCaseCharacter(prevNameCharacter);
 
@@ -98,6 +112,12 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
             ) {
                 return false;
             }
+
+            isIncrementedPrevCharacter = nextNameCharacter > prevNameCharacter;
+
+            if (nextNameCharacter < prevNameCharacter) {
+                return false;
+            }
         }
 
         return nextName > prevName;
@@ -111,6 +131,14 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
         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
      * 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 {
     /**
      * @param {Node} node
      * @returns {boolean}
      */
-    check: TNodeGuard;
+    check: TObfuscatingGuard;
 }

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

@@ -18,6 +18,7 @@ export interface IOptions {
     readonly debugProtectionInterval: boolean;
     readonly disableConsoleOutput: boolean;
     readonly domainLock: string[];
+    readonly forceTransformStrings: string[];
     readonly identifierNamesGenerator: TypeFromEnum<typeof IdentifierNamesGenerator>;
     readonly identifiersDictionary: 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 { NodeGuards } from '../../../node/NodeGuards';
+import { NodeLiteralUtils } from '../../../node/NodeLiteralUtils';
 import { StringLiteralControlFlowStorageCallNode } from '../../../custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/StringLiteralControlFlowStorageCallNode';
 import { StringLiteralNode } from '../../../custom-nodes/control-flow-flattening-nodes/StringLiteralNode';
 
@@ -55,7 +56,7 @@ export class StringLiteralControlFlowReplacer extends AbstractControlFlowReplace
             return literalNode;
         }
 
-        if (typeof literalNode.value !== 'string' || literalNode.value.length < 3) {
+        if (!NodeLiteralUtils.isStringLiteralNode(literalNode) || literalNode.value.length < 3) {
             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 ESTree from 'estree';
+import * as stringz from 'stringz';
 
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
@@ -48,11 +49,16 @@ export class SplitStringTransformer extends AbstractNodeTransformer {
 
     /**
      * @param {string} string
+     * @param {number} stringLength
      * @param {number} chunkSize
      * @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[] = [];
 
         let nextChunkStartIndex: number = 0;
@@ -62,7 +68,7 @@ export class SplitStringTransformer extends AbstractNodeTransformer {
             chunkIndex < chunksCount;
             ++chunkIndex, nextChunkStartIndex += chunkSize
         ) {
-            chunks[chunkIndex] = string.substr(nextChunkStartIndex, chunkSize);
+            chunks[chunkIndex] = stringz.substr(string, nextChunkStartIndex, chunkSize);
         }
 
         return chunks;
@@ -140,16 +146,19 @@ export class SplitStringTransformer extends AbstractNodeTransformer {
         parentNode: ESTree.Node,
         chunkLength: number
     ): ESTree.Node {
-        if (typeof literalNode.value !== 'string') {
+        if (!NodeLiteralUtils.isStringLiteralNode(literalNode)) {
             return literalNode;
         }
 
-        if (chunkLength >= literalNode.value.length) {
+        const valueLength: number = stringz.length(literalNode.value);
+
+        if (chunkLength >= valueLength) {
             return literalNode;
         }
 
         const stringChunks: string[] = SplitStringTransformer.chunkString(
             literalNode.value,
+            valueLength,
             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.isAwaitExpressionNode(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 * as ESTree from 'estree';
-import jsStringEscape from 'js-string-escape';
 
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
@@ -15,6 +14,7 @@ import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeFactory } from '../../node/NodeFactory';
 import { NodeGuards } from '../../node/NodeGuards';
 import { NodeUtils } from '../../node/NodeUtils';
+import { StringUtils } from '../../utils/StringUtils';
 
 @injectable()
 export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
@@ -22,6 +22,7 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
      * @type {NodeTransformer.ParentificationTransformer[]}
      */
     public readonly runAfter: NodeTransformer[] = [
+        NodeTransformer.EscapeSequenceTransformer,
         NodeTransformer.ParentificationTransformer,
         NodeTransformer.VariablePreserveTransformer
     ];
@@ -179,7 +180,7 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
         return NodeFactory.callExpressionNode(
             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 { 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 { ObfuscatingGuard } from '../../enums/node-transformers/preparing-transformers/obfuscating-guards/ObfuscatingGuard';
+import { ObfuscatingGuardResult } from '../../enums/node/ObfuscatingGuardResult';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
@@ -29,6 +30,7 @@ export class ObfuscatingGuardsTransformer extends AbstractNodeTransformer {
     private static readonly obfuscatingGuardsList: ObfuscatingGuard[] = [
         ObfuscatingGuard.BlackListObfuscatingGuard,
         ObfuscatingGuard.ConditionalCommentObfuscatingGuard,
+        ObfuscatingGuard.ForceTransformStringObfuscatingGuard,
         ObfuscatingGuard.ReservedStringObfuscatingGuard
     ];
 
@@ -84,13 +86,39 @@ export class ObfuscatingGuardsTransformer extends AbstractNodeTransformer {
      * @returns {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;
     }
+
+    /**
+     * @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 { TNodeGuard } from '../../../types/node/TNodeGuard';
-
 import { IObfuscatingGuard } from '../../../interfaces/node-transformers/preparing-transformers/obfuscating-guards/IObfuscatingGuard';
 
+import { ObfuscatingGuardResult } from '../../../enums/node/ObfuscatingGuardResult';
+
 import { NodeGuards } from '../../../node/NodeGuards';
 
 @injectable()
@@ -13,7 +13,7 @@ export class BlackListObfuscatingGuard implements IObfuscatingGuard {
     /**
      * @type {((node: Node) => boolean)[]}
      */
-    private static readonly blackListGuards: TNodeGuard[] = [
+    private static readonly blackListGuards: ((node: ESTree.Node) => boolean)[] = [
         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++) {
             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 { ObfuscatingGuardResult } from '../../../enums/node/ObfuscatingGuardResult';
+
 import { NodeGuards } from '../../../node/NodeGuards';
 
 @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 { IOptions } from '../../../interfaces/options/IOptions';
 
+import { ObfuscatingGuardResult } from '../../../enums/node/ObfuscatingGuardResult';
+
 import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 
 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 (
             this.options.reservedStrings.length
             && NodeGuards.isLiteralNode(node)
             && 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 { TStatement } from '../../types/node/TStatement';
 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 { IOptions } from '../../interfaces/options/IOptions';
@@ -21,14 +21,14 @@ import { IVisitedLexicalScopeNodesStackStorage } from '../../interfaces/storages
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 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 { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeAppender } from '../../node/NodeAppender';
 import { NodeGuards } from '../../node/NodeGuards';
-import { StringArrayScopeCallsWrapperVariableNode } from '../../custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperVariableNode';
 import { StringArrayScopeCallsWrapperFunctionNode } from '../../custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperFunctionNode';
+import { StringArrayScopeCallsWrapperVariableNode } from '../../custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperVariableNode';
 
 @injectable()
 export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransformer {
@@ -48,9 +48,9 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
     private readonly stringArrayScopeCallsWrapperNamesDataStorage: IStringArrayScopeCallsWrapperNamesDataStorage;
 
     /**
-     * @type {TStringArrayTransformerCustomNodeFactory}
+     * @type {TStringArrayCustomNodeFactory}
      */
-    private readonly stringArrayTransformerCustomNodeFactory: TStringArrayTransformerCustomNodeFactory;
+    private readonly stringArrayTransformerCustomNodeFactory: TStringArrayCustomNodeFactory;
 
     /**
      * @type {IVisitedLexicalScopeNodesStackStorage}
@@ -64,7 +64,7 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
      * @param {IStringArrayStorage} stringArrayStorage
      * @param {IStringArrayScopeCallsWrapperNamesDataStorage} stringArrayScopeCallsWrapperNamesDataStorage
      * @param {IStringArrayScopeCallsWrapperLexicalScopeDataStorage} stringArrayScopeCallsWrapperLexicalScopeDataStorage
-     * @param {TStringArrayTransformerCustomNodeFactory} stringArrayTransformerCustomNodeFactory
+     * @param {TStringArrayCustomNodeFactory} stringArrayTransformerCustomNodeFactory
      */
     public constructor (
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
@@ -73,8 +73,8 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
         @inject(ServiceIdentifiers.IStringArrayStorage) stringArrayStorage: IStringArrayStorage,
         @inject(ServiceIdentifiers.IStringArrayScopeCallsWrapperNamesDataStorage) stringArrayScopeCallsWrapperNamesDataStorage: IStringArrayScopeCallsWrapperNamesDataStorage,
         @inject(ServiceIdentifiers.IStringArrayScopeCallsWrapperLexicalScopeDataStorage) stringArrayScopeCallsWrapperLexicalScopeDataStorage: IStringArrayScopeCallsWrapperLexicalScopeDataStorage,
-        @inject(ServiceIdentifiers.Factory__IStringArrayTransformerCustomNode)
-            stringArrayTransformerCustomNodeFactory: TStringArrayTransformerCustomNodeFactory
+        @inject(ServiceIdentifiers.Factory__IStringArrayCustomNode)
+            stringArrayTransformerCustomNodeFactory: TStringArrayCustomNodeFactory
     ) {
         super(randomGenerator, options);
 
@@ -278,7 +278,7 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
     ): TStatement[] {
         const stringArrayScopeCallsWrapperVariableNode: ICustomNode<TInitialData<StringArrayScopeCallsWrapperVariableNode>> =
             this.stringArrayTransformerCustomNodeFactory(
-                StringArrayTransformerCustomNode.StringArrayScopeCallsWrapperVariableNode
+                StringArrayCustomNode.StringArrayScopeCallsWrapperVariableNode
             );
 
         stringArrayScopeCallsWrapperVariableNode.initialize(
@@ -302,7 +302,7 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
     ): TStatement[] {
         const stringArrayScopeCallsWrapperFunctionNode: ICustomNode<TInitialData<StringArrayScopeCallsWrapperFunctionNode>> =
             this.stringArrayTransformerCustomNodeFactory(
-                StringArrayTransformerCustomNode.StringArrayScopeCallsWrapperFunctionNode
+                StringArrayCustomNode.StringArrayScopeCallsWrapperFunctionNode
             );
 
         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 { TStatement } from '../../types/node/TStatement';
 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 { IEscapeSequenceEncoder } from '../../interfaces/utils/IEscapeSequenceEncoder';
 import { IIdentifierNamesGenerator } from '../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
 import { ILiteralNodesCacheStorage } from '../../interfaces/storages/string-array-transformers/ILiteralNodesCacheStorage';
 import { IOptions } from '../../interfaces/options/IOptions';
@@ -26,11 +25,10 @@ import { IVisitedLexicalScopeNodesStackStorage } from '../../interfaces/storages
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 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 { AbstractNodeTransformer } from '../AbstractNodeTransformer';
-import { NodeFactory } from '../../node/NodeFactory';
 import { NodeGuards } from '../../node/NodeGuards';
 import { NodeLiteralUtils } from '../../node/NodeLiteralUtils';
 import { NodeMetadata } from '../../node/NodeMetadata';
@@ -49,10 +47,6 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
      */
     private static readonly maxShiftedIndexValue: number = 1000;
 
-    /**
-     * @type {IEscapeSequenceEncoder}
-     */
-    private readonly escapeSequenceEncoder: IEscapeSequenceEncoder;
 
     /**
      * @type {IIdentifierNamesGenerator}
@@ -85,9 +79,9 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
     private readonly stringArrayScopeCallsWrapperNamesDataStorage: IStringArrayScopeCallsWrapperNamesDataStorage;
 
     /**
-     * @type {TStringArrayTransformerCustomNodeFactory}
+     * @type {TStringArrayCustomNodeFactory}
      */
-    private readonly stringArrayTransformerCustomNodeFactory: TStringArrayTransformerCustomNodeFactory;
+    private readonly stringArrayTransformerCustomNodeFactory: TStringArrayCustomNodeFactory;
 
     /**
      * @type {IVisitedLexicalScopeNodesStackStorage}
@@ -97,7 +91,6 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
     /**
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
-     * @param {IEscapeSequenceEncoder} escapeSequenceEncoder
      * @param {ILiteralNodesCacheStorage} literalNodesCacheStorage
      * @param {IVisitedLexicalScopeNodesStackStorage} visitedLexicalScopeNodesStackStorage
      * @param {IStringArrayStorage} stringArrayStorage
@@ -105,12 +98,11 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
      * @param {IStringArrayScopeCallsWrapperLexicalScopeDataStorage} stringArrayScopeCallsWrapperLexicalScopeDataStorage
      * @param {IStringArrayStorageAnalyzer} stringArrayStorageAnalyzer
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
-     * @param {TStringArrayTransformerCustomNodeFactory} stringArrayTransformerCustomNodeFactory
+     * @param {TStringArrayCustomNodeFactory} stringArrayTransformerCustomNodeFactory
      */
     public constructor (
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
         @inject(ServiceIdentifiers.IOptions) options: IOptions,
-        @inject(ServiceIdentifiers.IEscapeSequenceEncoder) escapeSequenceEncoder: IEscapeSequenceEncoder,
         @inject(ServiceIdentifiers.ILiteralNodesCacheStorage) literalNodesCacheStorage: ILiteralNodesCacheStorage,
         @inject(ServiceIdentifiers.IVisitedLexicalScopeNodesStackStorage) visitedLexicalScopeNodesStackStorage: IVisitedLexicalScopeNodesStackStorage,
         @inject(ServiceIdentifiers.IStringArrayStorage) stringArrayStorage: IStringArrayStorage,
@@ -121,12 +113,11 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
         @inject(ServiceIdentifiers.IStringArrayStorageAnalyzer) stringArrayStorageAnalyzer: IStringArrayStorageAnalyzer,
         @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
             identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory,
-        @inject(ServiceIdentifiers.Factory__IStringArrayTransformerCustomNode)
-            stringArrayTransformerCustomNodeFactory: TStringArrayTransformerCustomNodeFactory
+        @inject(ServiceIdentifiers.Factory__IStringArrayCustomNode)
+            stringArrayTransformerCustomNodeFactory: TStringArrayCustomNodeFactory
     ) {
         super(randomGenerator, options);
 
-        this.escapeSequenceEncoder = escapeSequenceEncoder;
         this.literalNodesCacheStorage = literalNodesCacheStorage;
         this.visitedLexicalScopeNodesStackStorage = visitedLexicalScopeNodesStackStorage;
         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:
                 return null;
         }
@@ -191,7 +173,10 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
      * @returns {NodeGuards}
      */
     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;
         }
 
@@ -208,7 +193,7 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
 
         const resultNode: ESTree.Node = stringArrayStorageItemData
             ? this.getStringArrayCallNode(stringArrayStorageItemData)
-            : this.getLiteralNode(literalValue);
+            : literalNode;
 
         this.literalNodesCacheStorage.set(cacheKey, resultNode);
 
@@ -217,14 +202,6 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
         return resultNode;
     }
 
-    /**
-     * @param {string} value
-     * @returns {Node}
-     */
-    private getLiteralNode (value: string): ESTree.Node {
-        return NodeFactory.literalNode(value);
-    }
-
     /**
      * @param {IStringArrayStorageItemData} stringArrayStorageItemData
      * @returns {Node}
@@ -234,7 +211,7 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
         const {decodeKey } = stringArrayStorageItemData;
 
         const stringArrayCallCustomNode: ICustomNode<TInitialData<StringArrayCallNode>> =
-            this.stringArrayTransformerCustomNodeFactory(StringArrayTransformerCustomNode.StringArrayCallNode);
+            this.stringArrayTransformerCustomNodeFactory(StringArrayCustomNode.StringArrayCallNode);
 
         stringArrayCallCustomNode.initialize(stringArrayCallsWrapperName, index, decodeKey);
 
@@ -403,22 +380,4 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
 
         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);
     }
 
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isForOfStatementNode (node: ESTree.Node): node is ESTree.ForOfStatement {
+        return node.type === NodeType.ForOfStatement;
+    }
+
     /**
      * @param {Node} node
      * @returns {boolean}

+ 8 - 0
src/node/NodeLiteralUtils.ts

@@ -3,6 +3,14 @@ import * as ESTree from 'estree';
 import { NodeGuards } from './NodeGuards';
 
 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 {Node} parentNode

+ 8 - 0
src/node/NodeMetadata.ts

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

+ 2 - 2
src/node/NodeUtils.ts

@@ -131,13 +131,13 @@ export class NodeUtils {
                     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;
 
                 if (value === null || value instanceof RegExp) {
                     clonedValue = value;
-                } else if (Array.isArray(value)) {
+                } else if (value instanceof Array) {
                     clonedValue = value.map(NodeUtils.cloneRecursive);
                 } else if (typeof value === 'object') {
                     clonedValue = NodeUtils.cloneRecursive(value);

+ 10 - 0
src/options/Options.ts

@@ -128,6 +128,16 @@ export class Options implements IOptions {
     ])
     public readonly domainLock!: string[];
 
+    /**
+     * @type {string[]}
+     */
+    @IsArray()
+    @ArrayUnique()
+    @IsString({
+        each: true
+    })
+    public readonly forceTransformStrings!: string[];
+
     /**
      * @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 { StringArrayRule } from './normalizer-rules/StringArrayRule';
 import { StringArrayEncodingRule } from './normalizer-rules/StringArrayEncodingRule';
-import { StringArrayThresholdRule } from './normalizer-rules/StringArrayThresholdRule';
 import { StringArrayWrappersChainedCallsRule } from './normalizer-rules/StringArrayWappersChainedCalls';
 
 @injectable()
@@ -38,7 +37,6 @@ export class OptionsNormalizer implements IOptionsNormalizer {
         SplitStringsChunkLengthRule,
         StringArrayRule,
         StringArrayEncodingRule,
-        StringArrayThresholdRule,
         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,
     domainLock: [],
     exclude: [],
+    forceTransformStrings: [],
     identifierNamesGenerator: IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator,
     identifiersPrefix: '',
     identifiersDictionary: [],

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

@@ -17,6 +17,7 @@ export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({
     disableConsoleOutput: false,
     domainLock: [],
     exclude: [],
+    forceTransformStrings: [],
     identifierNamesGenerator: IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator,
     identifiersPrefix: '',
     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 { ICryptUtilsSwappedAlphabet } from '../../interfaces/utils/ICryptUtilsSwappedAlphabet';
 import { IEncodedValue } from '../../interfaces/IEncodedValue';
-import { IEscapeSequenceEncoder } from '../../interfaces/utils/IEscapeSequenceEncoder';
 import { IIdentifierNamesGenerator } from '../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
@@ -55,11 +54,6 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
      */
     private readonly cryptUtilsSwappedAlphabet: ICryptUtilsSwappedAlphabet;
 
-    /**
-     * @type {IEscapeSequenceEncoder}
-     */
-    private readonly escapeSequenceEncoder: IEscapeSequenceEncoder;
-
     /**
      * @type {IIdentifierNamesGenerator}
      */
@@ -96,7 +90,6 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
      * @param {ICryptUtilsSwappedAlphabet} cryptUtilsSwappedAlphabet
-     * @param {IEscapeSequenceEncoder} escapeSequenceEncoder
      */
     public constructor (
         @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
@@ -104,15 +97,13 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
         @inject(ServiceIdentifiers.IArrayUtils) arrayUtils: IArrayUtils,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
         @inject(ServiceIdentifiers.IOptions) options: IOptions,
-        @inject(ServiceIdentifiers.ICryptUtilsSwappedAlphabet) cryptUtilsSwappedAlphabet: ICryptUtilsSwappedAlphabet,
-        @inject(ServiceIdentifiers.IEscapeSequenceEncoder) escapeSequenceEncoder: IEscapeSequenceEncoder
+        @inject(ServiceIdentifiers.ICryptUtilsSwappedAlphabet) cryptUtilsSwappedAlphabet: ICryptUtilsSwappedAlphabet
     ) {
         super(randomGenerator, options);
 
         this.identifierNamesGenerator = identifierNamesGeneratorFactory(options);
         this.arrayUtils = arrayUtils;
         this.cryptUtilsSwappedAlphabet = cryptUtilsSwappedAlphabet;
-        this.escapeSequenceEncoder = escapeSequenceEncoder;
 
         this.rc4Keys = this.randomGenerator.getRandomGenerator()
             .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
      * @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/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 { IdentifierNamesGenerator } from '../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
-import { StringArrayWrappersType } from '../../src/enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 
 (function () {
     const JavaScriptObfuscator: any = require('../../index');
 
     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,
@@ -21,9 +18,7 @@ import { StringArrayWrappersType } from '../../src/enums/node-transformers/strin
             identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
             stringArray: true,
             stringArrayThreshold: 1,
-            stringArrayWrappersChainedCalls: true,
-            stringArrayWrappersCount: 1,
-            stringArrayWrappersType: StringArrayWrappersType.Function
+            unicodeEscapeSequence: false
         }
     ).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 fixturesDirName: string = 'test/fixtures';
+    const fixturesDirName: string = path.join('test', 'fixtures');
     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 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 configFilePath: string = `${configDirName}/${configFileName}`;
+    const configFilePath: string = path.join(configDirName, configFileName);
 
 
     describe('run', () => {
@@ -66,7 +66,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         isFileExist: boolean;
 
                     before(() => {
-                        outputFixturesFilePath = `${fixturesDirName}/${outputFileName}`;
+                        outputFixturesFilePath = path.join(fixturesDirName, outputFileName);
 
                         JavaScriptObfuscatorCLI.obfuscate([
                             'node',
@@ -93,7 +93,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         testFunc = () => JavaScriptObfuscatorCLI.obfuscate([
                             'node',
                             '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`', () => {
                     const expectedError: RegExp = /Given input path must be a valid/;
                     const outputFileName: string = 'sample-obfuscated.ts';
-                    const outputFilePath: string = `${outputDirName}/${outputFileName}`;
+                    const outputFilePath: string = path.join(outputDirName, outputFileName);
 
                     let testFunc: () => void;
 
@@ -141,7 +141,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                             '--output',
                             outputFilePath,
                             '--exclude',
-                            '**/foo.js'
+                            path.join('**', 'foo.js')
                         ]);
 
                         isFileExist = fs.existsSync(outputFilePath);
@@ -163,7 +163,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                             '--output',
                             outputFilePath,
                             '--exclude',
-                            '**/sample.js'
+                            path.join('**', 'sample.js')
                         ]);
                     });
 
@@ -180,7 +180,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
 
         describe('Variant #2: obfuscation of directory', () => {
             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 outputFileName2: string = 'bar-obfuscated.js';
                 const outputFileName3: string = 'baz-obfuscated.js';
@@ -198,9 +198,9 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                     fileContent2: string;
 
                 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([
                         'node',
@@ -245,7 +245,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
             });
 
             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 outputFileName1: string = 'foo-obfuscated.js';
                 const outputFileName2: string = 'bar-obfuscated.js';
@@ -261,8 +261,8 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                     fileContent2: string;
 
                 before(() => {
-                    outputFixturesFilePath1 = `${directoryPath}/${outputFileName1}`;
-                    outputFixturesFilePath2 = `${directoryPath}/${outputFileName2}`;
+                    outputFixturesFilePath1 = path.join(directoryPath, outputFileName1);
+                    outputFixturesFilePath2 = path.join(directoryPath, outputFileName2);
 
                     JavaScriptObfuscatorCLI.obfuscate([
                         'node',
@@ -304,9 +304,9 @@ describe('JavaScriptObfuscatorCLI', function (): void {
             });
 
             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 outputDirectoryPath: string = `${directoryPath}/${outputDirectoryName}`;
+                const outputDirectoryPath: string = path.join(directoryPath, outputDirectoryName);
                 const outputFileName1: string = 'foo.js';
                 const outputFileName2: string = 'bar.js';
                 const outputFileName3: string = 'baz.js';
@@ -319,9 +319,9 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                     isFileExist3: boolean;
 
                 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([
                         'node',
@@ -338,7 +338,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
 
                 it(
                     `should create file \`${outputFileName1}\` with obfuscated code in ` +
-                    `\`${fixturesDirName}/${outputDirectoryName}\` directory`,
+                    `\`${path.join(fixturesDirName, outputDirectoryName)}\` directory`,
                     () => {
                         assert.equal(isFileExist1, true);
                     }
@@ -346,7 +346,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
 
                 it(
                     `should create file \`${outputFileName2}\` with obfuscated code in ` +
-                    `\`${fixturesDirName}/${outputDirectoryName}\` directory`,
+                    `\`${path.join(fixturesDirName, outputDirectoryName)}\` directory`,
                     () => {
                         assert.equal(isFileExist2, true);
                     }
@@ -354,7 +354,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
 
                 it(
                     `shouldn't create file \`${outputFileName3}\` in ` +
-                    `\`${fixturesDirName}/${outputDirectoryName}\` directory`,
+                    `\`${path.join(fixturesDirName, outputDirectoryName)}\` directory`,
                     () => {
                         assert.equal(isFileExist3, false);
                     }
@@ -367,7 +367,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
 
             describe('Variant #4: --exclude option', () => {
                 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 outputFileName2: string = 'bar-obfuscated.js';
                     const outputFileName3: string = 'baz-obfuscated.js';
@@ -385,16 +385,16 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         fileContent2: string;
 
                     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([
                             'node',
                             'javascript-obfuscator',
                             directoryPath,
                             '--exclude',
-                            '**/bark.js',
+                            path.join('**', 'bark.js'),
                             '--rename-globals',
                             'true'
                         ]);
@@ -434,7 +434,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                 });
 
                 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 outputFileName2: string = 'bar-obfuscated.js';
                     const outputFileName3: string = 'baz-obfuscated.js';
@@ -450,16 +450,16 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         fileContent1: string;
 
                     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([
                             'node',
                             'javascript-obfuscator',
                             directoryPath,
                             '--exclude',
-                            '**/foo.js',
+                            path.join('**', 'foo.js'),
                             '--rename-globals',
                             'true'
                         ]);
@@ -619,7 +619,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                     const expectedSourceMapSourceName: string = path.basename(fixtureFileName);
                     const sourceMapFileName: string = 'test';
                     const sourceMapFilePath: string = `${sourceMapFileName}.js.map`;
-                    const outputSourceMapFilePath: string = `${outputDirName}/${sourceMapFilePath}`;
+                    const outputSourceMapFilePath: string = path.join(outputDirName, sourceMapFilePath);
 
                     let isFileExist: boolean,
                         sourceMapObject: any;
@@ -864,7 +864,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
             });
 
             describe('`--exclude` option', () => {
-                const directoryPath: string = `${fixturesDirName}/directory-obfuscation`;
+                const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation');
                 const outputFileName1: string = 'foo-obfuscated.js';
                 const outputFileName2: string = 'bar-obfuscated.js';
 
@@ -874,8 +874,8 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                     isFileExist2: boolean;
 
                 before(() => {
-                    outputFixturesFilePath1 = `${directoryPath}/${outputFileName1}`;
-                    outputFixturesFilePath2 = `${directoryPath}/${outputFileName2}`;
+                    outputFixturesFilePath1 = path.join(directoryPath, outputFileName1);
+                    outputFixturesFilePath2 = path.join(directoryPath, outputFileName2);
 
                     JavaScriptObfuscatorCLI.obfuscate([
                         'node',
@@ -942,17 +942,17 @@ describe('JavaScriptObfuscatorCLI', function (): void {
 
         describe('Logging', () => {
             describe('Obfuscating file message', () => {
-                const directoryPath: string = `${fixturesDirName}/directory-obfuscation`;
+                const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation');
 
                 const inputFileName1: string = 'foo.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 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 expectedLoggingMessage2: string = `[javascript-obfuscator-cli] Obfuscating file: ${inputFilePath2}...`;
@@ -992,10 +992,10 @@ describe('JavaScriptObfuscatorCLI', function (): void {
             });
 
             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 inputFilePath: string = `${directoryPath}/${inputFileName}`;
+                const inputFilePath: string = path.join(directoryPath, inputFileName);
 
                 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';
 
 describe('HashbangOperatorTransformer', () => {
+    const lineSeparator: string = '\r?\n';
+
     describe('Variant #1: simple', () => {
         const regExp: RegExp = new RegExp(
-            `^#!\/usr\/bin\/env node\n` +
+            `^#!\/usr\/bin\/env node${lineSeparator}` +
             `var foo *= *'abc';`
         );
 
@@ -33,7 +35,7 @@ describe('HashbangOperatorTransformer', () => {
 
     describe('Variant #2: multiple new lines', () => {
         const regExp: RegExp = new RegExp(
-            `^#!\/usr\/bin\/env node\n\n\n\n` +
+            `^#!\/usr\/bin\/env node${lineSeparator.repeat(4)}` +
             `var foo *= *'abc';`
         );
 
@@ -57,7 +59,7 @@ describe('HashbangOperatorTransformer', () => {
 
     describe('Variant #3: `stringArray` option enabled', () => {
         const regExp: RegExp = new RegExp(
-            `^#!\/usr\/bin\/env node\n` +
+            `^#!\/usr\/bin\/env node${lineSeparator}` +
             `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 () {
             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 { StringArrayEncoding } from '../../../../../src/enums/node-transformers/string-array-transformers/StringArrayEncoding';
+
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../../helpers/readFileAsString';
@@ -166,7 +168,7 @@ describe('SplitStringTransformer', () => {
     describe('Variant #10: string with emoji', () => {
         describe('Variant #1: single 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');
 
@@ -204,7 +206,7 @@ describe('SplitStringTransformer', () => {
 
         describe('Variant #2: multiple 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');
 
@@ -239,9 +241,79 @@ describe('SplitStringTransformer', () => {
                 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', () => {
             const regExp: RegExp = new RegExp(`` +
                 `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', () => {
             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', () => {
             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';/;
 
         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';/;
 
         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';/;
 
         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);
                 });
             });
+
+            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', () => {

+ 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';
 
 describe('CommentsTransformer', () => {
+    const lineSeparatorEscaped: string = '\\r?\\n';
+
     describe('Variant #1: simple comment without preserved words', () => {
         const regExp: RegExp = /^var test *= *0x1;$/;
 
@@ -72,10 +74,10 @@ describe('CommentsTransformer', () => {
 
     describe('Variant #4: comment with preserved and non-preserved words', () => {
         const regExp: RegExp = new RegExp(``+
-            `^\\/\\*\\* *\\n` +
-            ` *\\* *@license *\\n` +
-            ` *\\* *test\\n` +
-            ` *\\*\\/\\n` +
+            `^\\/\\*\\* *${lineSeparatorEscaped}` +
+            ` *\\* *@license *${lineSeparatorEscaped}` +
+            ` *\\* *test${lineSeparatorEscaped}` +
+            ` *\\*\\/${lineSeparatorEscaped}` +
             `var test *= *0x1;` +
             ` *\\/\\*\\* *@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$/;
 
         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', () => {
             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', () => {
                 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};' +
@@ -116,11 +116,11 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
             describe('Variant #1: option value is lower then count `literal` nodes in the scope', () => {
                 const stringArrayCallRegExp: RegExp = new RegExp(
                     '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', () => {
                 const stringArrayCallRegExp: RegExp = new RegExp(
                     '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', () => {
                 const stringArrayCallRegExp: RegExp = new RegExp(
                     '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(
                     'var c *= *b;' +
                     'if *\\(!!\\[]\\) *{' +
-                    'var foo *= *c\\(\'0x0\'\\);' +
+                        'var foo *= *c\\(\'0x0\'\\);' +
                     '}'
                 );
 
@@ -306,7 +306,7 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
 
         describe('Variant #4: prevailing kind of variables', () => {
             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};' +
@@ -368,22 +368,22 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
                         'const q *= *b;' +
                         'const foo *= *q\\(\'0x0\'\\);' +
                         '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: `1` scope calls wrapper for each encoding type', () => {
                 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};' +
                     // 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', () => {
                 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};' +
@@ -949,13 +949,13 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
             describe('Variant #1: `1` scope calls wrapper for each encoding type', () => {
                 const stringArrayWrappersRegExp: RegExp = new RegExp(
                     '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', () => {
                 const stringArrayWrappersRegExp: RegExp = new RegExp(
                     '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\'\\);' +
                     '}'
                 );
 

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác