Selaa lähdekoodia

Merge pull request #759 from javascript-obfuscator/obfuscating-guards-force-obfuscated-node-support

`forceTransformStrings` option
Timofey Kachalov 4 vuotta sitten
vanhempi
commit
2dc37c389f
63 muutettua tiedostoa jossa 1116 lisäystä ja 338 poistoa
  1. 4 0
      CHANGELOG.md
  2. 21 0
      README.md
  3. 0 0
      dist/index.browser.js
  4. 0 0
      dist/index.cli.js
  5. 0 0
      dist/index.js
  6. 1 1
      package.json
  7. 1 0
      src/JavaScriptObfuscator.ts
  8. 12 6
      src/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.ts
  9. 5 0
      src/cli/JavaScriptObfuscatorCLI.ts
  10. 1 1
      src/container/ServiceIdentifiers.ts
  11. 8 8
      src/container/modules/custom-nodes/CustomNodesModule.ts
  12. 10 0
      src/container/modules/node-transformers/FinalizingTransformersModule.ts
  13. 10 4
      src/container/modules/node-transformers/PreparingTransformersModule.ts
  14. 1 1
      src/container/modules/node-transformers/StringArrayTransformersModule.ts
  15. 1 0
      src/declarations/ESTree.d.ts
  16. 1 1
      src/enums/custom-nodes/StringArrayCustomNode.ts
  17. 1 0
      src/enums/node-transformers/NodeTransformer.ts
  18. 1 0
      src/enums/node-transformers/preparing-transformers/obfuscating-guards/ObfuscatingGuard.ts
  19. 5 0
      src/enums/node/ObfuscatingGuardResult.ts
  20. 2 2
      src/interfaces/node-transformers/preparing-transformers/obfuscating-guards/IObfuscatingGuard.ts
  21. 1 0
      src/interfaces/options/IOptions.ts
  22. 2 1
      src/node-transformers/control-flow-transformers/control-flow-replacers/StringLiteralControlFlowReplacer.ts
  23. 1 1
      src/node-transformers/converting-transformers/SplitStringTransformer.ts
  24. 82 0
      src/node-transformers/finalizing-transformers/EscapeSequenceTransformer.ts
  25. 34 6
      src/node-transformers/preparing-transformers/ObfuscatingGuardsTransformer.ts
  26. 8 8
      src/node-transformers/preparing-transformers/obfuscating-guards/BlackListObfuscatingGuard.ts
  27. 13 11
      src/node-transformers/preparing-transformers/obfuscating-guards/ConditionalCommentObfuscatingGuard.ts
  28. 58 0
      src/node-transformers/preparing-transformers/obfuscating-guards/ForceTransformStringObfuscatingGuard.ts
  29. 9 5
      src/node-transformers/preparing-transformers/obfuscating-guards/ReservedStringObfuscatingGuard.ts
  30. 10 10
      src/node-transformers/string-array-transformers/StringArrayScopeCallsWrapperTransformer.ts
  31. 13 54
      src/node-transformers/string-array-transformers/StringArrayTransformer.ts
  32. 8 0
      src/node/NodeLiteralUtils.ts
  33. 8 0
      src/node/NodeMetadata.ts
  34. 10 0
      src/options/Options.ts
  35. 0 2
      src/options/OptionsNormalizer.ts
  36. 0 27
      src/options/normalizer-rules/StringArrayThresholdRule.ts
  37. 1 0
      src/options/presets/Default.ts
  38. 1 0
      src/options/presets/NoCustomNodes.ts
  39. 7 0
      src/types/container/custom-nodes/TStringArrayCustomNodeFactory.ts
  40. 0 7
      src/types/container/custom-nodes/TStringArrayTransformerCustomNodeFactory.ts
  41. 0 3
      src/types/node/TNodeGuard.ts
  42. 5 0
      src/types/node/TObfuscatingGuard.ts
  43. 2 5
      test/dev/dev.ts
  44. 206 0
      test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/EscapeSequenceTransformer.spec.ts
  45. 0 0
      test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/error-when-non-latin.js
  46. 0 0
      test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/force-transform-strings-option.js
  47. 2 0
      test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/reserved-strings-option-1.js
  48. 0 0
      test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/reserved-strings-option-2.js
  49. 1 0
      test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/simple-input.js
  50. 9 0
      test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/fixtures/wrappers-count.js
  51. 69 0
      test/functional-tests/node-transformers/preparing-transformers/obfuscating-guards/force-transform-string-obfuscating-guard/ForceTransformStringObfuscatingGuard.spec.ts
  52. 2 0
      test/functional-tests/node-transformers/preparing-transformers/obfuscating-guards/force-transform-string-obfuscating-guard/fixtures/base-behaviour.js
  53. 221 123
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts
  54. 5 0
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/force-transform-strings-option-conditional-comments.js
  55. 2 0
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/force-transform-strings-option.js
  56. 2 0
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/reserved-strings-option.js
  57. 0 28
      test/functional-tests/options/OptionsNormalizer.spec.ts
  58. 2 0
      test/index.spec.ts
  59. 85 1
      test/unit-tests/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.spec.ts
  60. 17 16
      test/unit-tests/generators/identifier-names-generators/MangledlIdentifierNamesGenerator.spec.ts
  61. 73 6
      test/unit-tests/node-transformers/preparing-transformers/ObfuscatingGuardsTransformer.spec.ts
  62. 44 0
      test/unit-tests/node/node-literal-utils/NodeLiteralUtils.spec.ts
  63. 18 0
      test/unit-tests/node/node-metadata/NodeMetadata.spec.ts

+ 4 - 0
CHANGELOG.md

@@ -1,5 +1,9 @@
 Change Log
 
+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

+ 21 - 0
README.md

@@ -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>
@@ -656,6 +658,25 @@ 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`
 

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
dist/index.browser.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
dist/index.cli.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
dist/index.js


+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "2.3.1",
+  "version": "2.4.0",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",

+ 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. ' +

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

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

+ 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'
+}

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

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

@@ -140,7 +140,7 @@ export class SplitStringTransformer extends AbstractNodeTransformer {
         parentNode: ESTree.Node,
         chunkLength: number
     ): ESTree.Node {
-        if (typeof literalNode.value !== 'string') {
+        if (!NodeLiteralUtils.isStringLiteralNode(literalNode)) {
             return literalNode;
         }
 

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

@@ -0,0 +1,82 @@
+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 { 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 {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 newLiteralNode: ESTree.Literal = NodeFactory.literalNode(
+            this.escapeSequenceEncoder.encode(
+                literalNode.value,
+                this.options.unicodeEscapeSequence
+            )
+        );
+
+        NodeUtils.parentizeNode(newLiteralNode, parentNode);
+
+        return newLiteralNode;
+    }
+}

+ 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/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}

+ 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: [],

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

+ 2 - 5
test/dev/dev.ts

@@ -2,14 +2,13 @@
 
 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';
+            const foo = 'foo test';
 
             function test () {
                 const bar = 'bar';
@@ -21,9 +20,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();
 

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

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

+ 221 - 123
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts

@@ -63,28 +63,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #3: 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 #4: same literal node values', () => {
+    describe('Variant #3: same literal node values', () => {
         const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['test'\];/;
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0'\);/;
 
@@ -112,59 +91,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #5: `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 #6: `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 #7: short literal node value', () => {
+    describe('Variant #4: short literal node value', () => {
         const regExp: RegExp = /var test *= *'te';/;
 
         let obfuscatedCode: string;
@@ -187,7 +114,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #8: base64 encoding', () => {
+    describe('Variant #5: base64 encoding', () => {
         const stringArrayRegExp: RegExp = new RegExp(`^var _0x([a-f0-9]){4} *= *\\['${swapLettersCase('dGVzdA==')}'];`);
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0'\);/;
 
@@ -216,7 +143,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #9: rc4 encoding', () => {
+    describe('Variant #6: rc4 encoding', () => {
         describe('Variant #1: single string literal', () => {
             const regExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0', *'.{4}'\);/;
 
@@ -282,7 +209,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #10: none and base64 encoding', () => {
+    describe('Variant #7: none and base64 encoding', () => {
         describe('Variant #1: string array values', () => {
             const samplesCount: number = 300;
             const expectedMatchesChance: number = 0.5;
@@ -338,7 +265,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #11: none and rc4 encoding', () => {
+    describe('Variant #8: none and rc4 encoding', () => {
         describe('Variant #1: string array calls wrapper call', () => {
             const samplesCount: number = 300;
             const expectedMatchesChance: number = 0.5;
@@ -394,7 +321,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #12: base64 and rc4 encoding', () => {
+    describe('Variant #9: base64 and rc4 encoding', () => {
         describe('Variant #1: single string literal', () => {
             const samplesCount: number = 300;
             const expectedMatchesChance: number = 0.5;
@@ -450,7 +377,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #13: `stringArrayThreshold` option value', () => {
+    describe('Variant #10: `stringArrayThreshold` option value', () => {
         const samples: number = 1000;
         const stringArrayThreshold: number = 0.5;
         const delta: number = 0.1;
@@ -493,7 +420,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #14: string array calls wrapper name', () => {
+    describe('Variant #11: string array calls wrapper name', () => {
         const regExp: RegExp = /console\[b\('0x0'\)]\('a'\);/;
 
         let obfuscatedCode: string;
@@ -517,7 +444,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #15: `reservedStrings` option is enabled', () => {
+    describe('Variant #12: `reservedStrings` option is enabled', () => {
         describe('Variant #1: base `reservedStrings` values', () => {
             describe('Variant #1: single reserved string value', () => {
                 const stringLiteralRegExp1: RegExp = /const foo *= *'foo';/;
@@ -526,7 +453,7 @@ describe('StringArrayTransformer', function () {
                 let obfuscatedCode: string;
 
                 before(() => {
-                    const code: string = readFileAsString(__dirname + '/fixtures/reserved-strings-option-1.js');
+                    const code: string = readFileAsString(__dirname + '/fixtures/reserved-strings-option.js');
 
                     obfuscatedCode = JavaScriptObfuscator.obfuscate(
                         code,
@@ -555,7 +482,7 @@ describe('StringArrayTransformer', function () {
                 let obfuscatedCode: string;
 
                 before(() => {
-                    const code: string = readFileAsString(__dirname + '/fixtures/reserved-strings-option-1.js');
+                    const code: string = readFileAsString(__dirname + '/fixtures/reserved-strings-option.js');
 
                     obfuscatedCode = JavaScriptObfuscator.obfuscate(
                         code,
@@ -586,7 +513,7 @@ describe('StringArrayTransformer', function () {
                 let obfuscatedCode: string;
 
                 before(() => {
-                    const code: string = readFileAsString(__dirname + '/fixtures/reserved-strings-option-1.js');
+                    const code: string = readFileAsString(__dirname + '/fixtures/reserved-strings-option.js');
 
                     obfuscatedCode = JavaScriptObfuscator.obfuscate(
                         code,
@@ -615,7 +542,7 @@ describe('StringArrayTransformer', function () {
                 let obfuscatedCode: string;
 
                 before(() => {
-                    const code: string = readFileAsString(__dirname + '/fixtures/reserved-strings-option-1.js');
+                    const code: string = readFileAsString(__dirname + '/fixtures/reserved-strings-option.js');
 
                     obfuscatedCode = JavaScriptObfuscator.obfuscate(
                         code,
@@ -637,59 +564,230 @@ describe('StringArrayTransformer', function () {
                 });
             });
         });
+    });
 
-        describe('Variant #3: `unicodeEscapeSequence` option is enabled', () => {
-            const stringLiteralRegExp1: RegExp = /const foo *= *'foo';/;
-            const stringLiteralRegExp2: RegExp = /const bar *= *'\\x62\\x61\\x72';/;
+    describe('Variant #13: `forceTransformStrings` option is enabled', () => {
+        describe('Variant #1: base `forceTransformStrings` values', () => {
+            describe('Variant #1: single force transform string value', () => {
+                const stringLiteralRegExp1: RegExp = /const foo *= *'foo';/;
+                const stringLiteralRegExp2: RegExp = /const bar *= *_0x([a-f0-9]){4}\('0x0'\);/;
 
-            let obfuscatedCode: string;
+                let obfuscatedCode: string;
 
-            before(() => {
-                const code: string = readFileAsString(__dirname + '/fixtures/reserved-strings-option-1.js');
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/force-transform-strings-option.js');
 
-                obfuscatedCode = JavaScriptObfuscator.obfuscate(
-                    code,
-                    {
-                        ...NO_ADDITIONAL_NODES_PRESET,
-                        reservedStrings: ['foo'],
-                        unicodeEscapeSequence: true
-                    }
-                ).getObfuscatedCode();
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            forceTransformStrings: ['bar'],
+                            stringArray: true,
+                            stringArrayThreshold: 0
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('match #1: should not transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp1);
+                });
+
+                it('match #2: should transform force transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp2);
+                });
+            });
+
+            describe('Variant #2: two force transform string values', () => {
+                const stringLiteralRegExp1: RegExp = /const foo *= *_0x([a-f0-9]){4}\('0x0'\);/;
+                const stringLiteralRegExp2: RegExp = /const bar *= *_0x([a-f0-9]){4}\('0x1'\);/;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/force-transform-strings-option.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            forceTransformStrings: ['foo', 'bar'],
+                            stringArray: true,
+                            stringArrayThreshold: 0
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('match #1: should transform force transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp1);
+                });
+
+                it('match #2: should transform force transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp2);
+                });
+            });
+        });
+
+        describe('Variant #2: RegExp `forceTransformStrings` values', () => {
+            describe('Variant #1: single force transform string value', () => {
+                const stringLiteralRegExp1: RegExp = /const foo *= *'foo'/;
+                const stringLiteralRegExp2: RegExp = /const bar *= *_0x([a-f0-9]){4}\('0x0'\);/;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/force-transform-strings-option.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            forceTransformStrings: ['ar$'],
+                            stringArray: true,
+                            stringArrayThreshold: 0
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('match #1: should not transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp1);
+                });
+
+                it('match #2: should transform force transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp2);
+                });
             });
 
-            it('match #1: should ignore reserved strings', () => {
-                assert.match(obfuscatedCode, stringLiteralRegExp1);
+            describe('Variant #2: two force transform string values', () => {
+                const stringLiteralRegExp1: RegExp = /const foo *= *_0x([a-f0-9]){4}\('0x0'\);/;
+                const stringLiteralRegExp2: RegExp = /const bar *= *_0x([a-f0-9]){4}\('0x1'\);/;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/force-transform-strings-option.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            forceTransformStrings: ['^fo', '.ar'],
+                            stringArray: true,
+                            stringArrayThreshold: 1
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('match #1: should transform force transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp1);
+                });
+
+                it('match #2: should transform force transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp2);
+                });
             });
+        });
+
+        describe('Variant #3: `stringArray` option is disabled', () => {
+            describe('Variant #1: base case', () => {
+                const stringLiteralRegExp1: RegExp = /const foo *= *'foo';/;
+                const stringLiteralRegExp2: RegExp = /const bar *= *'bar';/;
 
-            it('match #2: should transform non-reserved strings', () => {
-                assert.match(obfuscatedCode, stringLiteralRegExp2);
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/force-transform-strings-option.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            forceTransformStrings: ['foo', 'bar'],
+                            stringArray: false,
+                            stringArrayThreshold: 0
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('match #1: should not transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp1);
+                });
+
+                it('match #2: should not transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp2);
+                });
             });
         });
 
-        describe('Variant #4: correct escape of special characters', () => {
-            const stringLiteralRegExp: RegExp = /var baz *= *'Cannot find module \\'' *\+ *foo *\+ *'\\x27';/;
+        describe('Variant #4: Priority over `reservedStrings` option', () => {
+            describe('Variant #1: base case', () => {
+                const stringLiteralRegExp1: RegExp = /const foo *= *'foo';/;
+                const stringLiteralRegExp2: RegExp = /const bar *= *_0x([a-f0-9]){4}\('0x0'\);/;
 
-            let obfuscatedCode: string;
+                let obfuscatedCode: string;
 
-            before(() => {
-                const code: string = readFileAsString(__dirname + '/fixtures/reserved-strings-option-2.js');
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/force-transform-strings-option.js');
 
-                obfuscatedCode = JavaScriptObfuscator.obfuscate(
-                    code,
-                    {
-                        ...NO_ADDITIONAL_NODES_PRESET,
-                        reservedStrings: ['a']
-                    }
-                ).getObfuscatedCode();
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            forceTransformStrings: ['bar'],
+                            reservedStrings: ['foo', 'bar'],
+                            stringArray: true,
+                            stringArrayThreshold: 1
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('match #1: should not transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp1);
+                });
+
+                it('match #2: should transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp2);
+                });
             });
+        });
+
+        describe('Variant #5: Priority on conditional comments', () => {
+            describe('Variant #1: base case', () => {
+                const stringLiteralRegExp1: RegExp = /const foo *= *'foo';/;
+                const stringLiteralRegExp2: RegExp = /const bar *= *'bar';/;
+                const stringLiteralRegExp3: RegExp = /const baz *= *_0x([a-f0-9]){4}\('0x0'\);/;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/force-transform-strings-option-conditional-comments.js');
 
-            it('match #1: should ignore reserved strings', () => {
-                assert.match(obfuscatedCode, stringLiteralRegExp);
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            forceTransformStrings: ['bar'],
+                            stringArray: true,
+                            stringArrayThreshold: 1
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('match #1: should not transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp1);
+                });
+
+                it('match #2: should not transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp2);
+                });
+
+                it('match #3: should transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp3);
+                });
             });
         });
     });
 
-    describe('Variant #16: object expression key literal', () => {
+    describe('Variant #14: object expression key literal', () => {
         describe('Variant #1: base key literal', () => {
             const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['bar'];/;
             const objectExpressionRegExp: RegExp = /var test *= *{'foo' *: *_0x([a-f0-9]){4}\('0x0'\)};/;
@@ -747,7 +845,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #17: import declaration source literal', () => {
+    describe('Variant #15: import declaration source literal', () => {
         const importDeclarationRegExp: RegExp = /import *{ *bar *} *from *'foo';/;
 
         let obfuscatedCode: string;
@@ -770,7 +868,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #18: export all declaration source literal', () => {
+    describe('Variant #16: export all declaration source literal', () => {
         const exportAllDeclarationRegExp: RegExp = /export *\* *from *'foo';/;
 
         let obfuscatedCode: string;
@@ -793,7 +891,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #19: export named declaration source literal', () => {
+    describe('Variant #17: export named declaration source literal', () => {
         const exportNamedDeclarationRegExp: RegExp = /export *{ *bar *} *from *'foo';/;
 
         let obfuscatedCode: string;

+ 5 - 0
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/force-transform-strings-option-conditional-comments.js

@@ -0,0 +1,5 @@
+// javascript-obfuscator:disable
+const foo = 'foo';
+const bar = 'bar';
+// javascript-obfuscator:enable
+const baz = 'baz';

+ 2 - 0
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/force-transform-strings-option.js

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

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

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

+ 0 - 28
test/functional-tests/options/OptionsNormalizer.spec.ts

@@ -587,34 +587,6 @@ describe('OptionsNormalizer', () => {
             });
         });
 
-        describe('stringArrayThresholdRule', () => {
-            before(() => {
-                optionsPreset = getNormalizedOptions({
-                    ...getDefaultOptions(),
-                    rotateStringArray: true,
-                    shuffleStringArray: true,
-                    stringArray: true,
-                    stringArrayWrappersChainedCalls: true,
-                    stringArrayWrappersCount: 5,
-                    stringArrayThreshold: 0
-                });
-
-                expectedOptionsPreset = {
-                    ...getDefaultOptions(),
-                    rotateStringArray: false,
-                    shuffleStringArray: false,
-                    stringArray: false,
-                    stringArrayWrappersChainedCalls: false,
-                    stringArrayWrappersCount: 0,
-                    stringArrayThreshold: 0
-                };
-            });
-
-            it('should normalize options preset', () => {
-                assert.deepEqual(optionsPreset, expectedOptionsPreset);
-            });
-        });
-
         describe('stringArrayWrappersChainedCallsRule', () => {
             before(() => {
                 optionsPreset = getNormalizedOptions({

+ 2 - 0
test/index.spec.ts

@@ -95,10 +95,12 @@ import './functional-tests/node-transformers/converting-transformers/object-patt
 import './functional-tests/node-transformers/converting-transformers/split-string-transformer/SplitStringTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/template-literal-transformer/TemplateLiteralTransformer.spec';
 import './functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec';
+import './functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/EscapeSequenceTransformer.spec';
 import './functional-tests/node-transformers/initializing-transformers/comments-transformer/CommentsTransformer.spec';
 import './functional-tests/node-transformers/preparing-transformers/eval-call-expression-transformer/EvalCallExpressionTransformer.spec';
 import './functional-tests/node-transformers/preparing-transformers/obfuscating-guards/black-list-obfuscating-guard/BlackListObfuscatingGuard.spec';
 import './functional-tests/node-transformers/preparing-transformers/obfuscating-guards/conditional-comment-obfuscating-guard/ConditionalCommentObfuscatingGuard.spec';
+import './functional-tests/node-transformers/preparing-transformers/obfuscating-guards/force-transform-string-obfuscating-guard/ForceTransformStringObfuscatingGuard.spec';
 import './functional-tests/node-transformers/preparing-transformers/obfuscating-guards/reserved-string-obfuscating-guard/ReservedStringObfuscatingGuard.spec';
 import './functional-tests/node-transformers/preparing-transformers/variable-preserve-transformer/VariablePreserveTransformer.spec';
 import './functional-tests/node-transformers/rename-identifiers-transformers/identifier-replacer/IdentifierReplacer.spec';

+ 85 - 1
test/unit-tests/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.spec.ts

@@ -213,7 +213,7 @@ describe('StringArrayStorageAnalyzer', () => {
             });
         });
 
-        describe('Analyzes of the AST tree with ignored nodes', () => {
+        describe('Analyzes of the AST tree with ignored string literal nodes', () => {
             const literalNode1: ESTree.Literal = NodeFactory.literalNode('foo');
             const literalNode2: ESTree.Literal = NodeFactory.literalNode('bar');
             NodeMetadata.set(literalNode2, {ignoredNode: true});
@@ -254,6 +254,90 @@ describe('StringArrayStorageAnalyzer', () => {
             });
         });
 
+        describe('Analyzes of the AST tree with force transform string literal nodes', () => {
+            describe('Variant #1: Force obfuscate string when threshold is `0`', () => {
+                const literalNode1: ESTree.Literal = NodeFactory.literalNode('foo');
+                const literalNode2: ESTree.Literal = NodeFactory.literalNode('bar');
+                NodeMetadata.set(literalNode2, {forceTransformNode: true});
+
+                const expectedStringArrayStorageItemData1: undefined = undefined;
+                const expectedStringArrayStorageItemData2: IStringArrayStorageItemData = {
+                    encodedValue: 'bar',
+                    encoding: StringArrayEncoding.None,
+                    decodeKey: null,
+                    index: 0,
+                    value: 'bar'
+                };
+
+                let stringArrayStorageItemData1: IStringArrayStorageItemData | undefined;
+                let stringArrayStorageItemData2: IStringArrayStorageItemData | undefined;
+
+                before(() => {
+                    stringArrayStorageAnalyzer = getStringArrayStorageAnalyzer({
+                        stringArrayThreshold: 0
+                    });
+
+                    const astTree: ESTree.Program = NodeFactory.programNode([
+                        NodeFactory.expressionStatementNode(literalNode1),
+                        NodeFactory.expressionStatementNode(literalNode2)
+                    ]);
+
+                    stringArrayStorageAnalyzer.analyze(astTree);
+                    stringArrayStorageItemData1 = stringArrayStorageAnalyzer.getItemDataForLiteralNode(literalNode1);
+                    stringArrayStorageItemData2 = stringArrayStorageAnalyzer.getItemDataForLiteralNode(literalNode2);
+                });
+
+                it('Variant #1: should return correct string array storage item data for literal node #1', () => {
+                    assert.deepEqual(stringArrayStorageItemData1, expectedStringArrayStorageItemData1);
+                });
+
+                it('Variant #2: should return correct string array storage item data for literal node #1', () => {
+                    assert.deepEqual(stringArrayStorageItemData2, expectedStringArrayStorageItemData2);
+                });
+            });
+
+            describe('Variant #2: Force obfuscate string when string value shorter than allowed length', () => {
+                const literalNode1: ESTree.Literal = NodeFactory.literalNode('a');
+                const literalNode2: ESTree.Literal = NodeFactory.literalNode('b');
+                NodeMetadata.set(literalNode2, {forceTransformNode: true});
+
+                const expectedStringArrayStorageItemData1: undefined = undefined;
+                const expectedStringArrayStorageItemData2: IStringArrayStorageItemData = {
+                    encodedValue: 'b',
+                    encoding: StringArrayEncoding.None,
+                    decodeKey: null,
+                    index: 0,
+                    value: 'b'
+                };
+
+                let stringArrayStorageItemData1: IStringArrayStorageItemData | undefined;
+                let stringArrayStorageItemData2: IStringArrayStorageItemData | undefined;
+
+                before(() => {
+                    stringArrayStorageAnalyzer = getStringArrayStorageAnalyzer({
+                        stringArrayThreshold: 1
+                    });
+
+                    const astTree: ESTree.Program = NodeFactory.programNode([
+                        NodeFactory.expressionStatementNode(literalNode1),
+                        NodeFactory.expressionStatementNode(literalNode2)
+                    ]);
+
+                    stringArrayStorageAnalyzer.analyze(astTree);
+                    stringArrayStorageItemData1 = stringArrayStorageAnalyzer.getItemDataForLiteralNode(literalNode1);
+                    stringArrayStorageItemData2 = stringArrayStorageAnalyzer.getItemDataForLiteralNode(literalNode2);
+                });
+
+                it('Variant #1: should return correct string array storage item data for literal node #1', () => {
+                    assert.deepEqual(stringArrayStorageItemData1, expectedStringArrayStorageItemData1);
+                });
+
+                it('Variant #2: should return correct string array storage item data for literal node #1', () => {
+                    assert.deepEqual(stringArrayStorageItemData2, expectedStringArrayStorageItemData2);
+                });
+            });
+        });
+
         /**
          * This test covers rare case when with random value inside `shouldAddValueToStringArray` was `0`
          * that trigger positive check for method.

+ 17 - 16
test/unit-tests/generators/identifier-names-generators/MangledlIdentifierNamesGenerator.spec.ts

@@ -174,6 +174,7 @@ describe('MangledIdentifierNamesGenerator', () => {
     describe('isIncrementedMangledName', function () {
         this.timeout(60000);
 
+        const samplesCount: number = 1000000;
         const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
 
         inversifyContainerFacade.load('', '', {});
@@ -182,29 +183,29 @@ describe('MangledIdentifierNamesGenerator', () => {
             IdentifierNamesGenerator.MangledIdentifierNamesGenerator
         );
 
+        let isSuccessComparison: boolean = true;
         let mangledName: string = '';
         let prevMangledName: string = '9';
 
-        for (let sample = 0; sample <= 10000; sample++) {
-            describe(`Variant #${sample + 1}`, () => {
-                let resultNormal: boolean;
-                let resultReversed: boolean;
+        for (let sample = 0; sample <= samplesCount; sample++) {
+            let resultNormal: boolean;
+            let resultReversed: boolean;
 
-                mangledName = identifierNamesGenerator.generateNext();
-                resultNormal = MangledIdentifierNamesGenerator.isIncrementedMangledName(mangledName, prevMangledName);
-                resultReversed = MangledIdentifierNamesGenerator.isIncrementedMangledName(prevMangledName, mangledName);
+            mangledName = identifierNamesGenerator.generateNext();
+            resultNormal = MangledIdentifierNamesGenerator.isIncrementedMangledName(mangledName, prevMangledName);
+            resultReversed = MangledIdentifierNamesGenerator.isIncrementedMangledName(prevMangledName, mangledName);
 
-                it(`Variant #1: should compare mangled names: ${mangledName}, ${prevMangledName}`, () => {
-                    assert.isTrue(resultNormal);
-                });
+            if (!resultNormal || resultReversed) {
+                isSuccessComparison = false;
+                break;
+            }
 
-                it(`Variant #2: should compare mangled names: ${prevMangledName}, ${mangledName}`, () => {
-                    assert.isFalse(resultReversed);
-                });
-
-                prevMangledName = mangledName;
-            });
+            prevMangledName = mangledName;
         }
+
+        it('should correctly compare mangled names', () => {
+            assert.isTrue(isSuccessComparison);
+        });
     });
 
     describe('isValidIdentifierName', () => {

+ 73 - 6
test/unit-tests/node-transformers/preparing-transformers/ObfuscatingGuardsTransformer.spec.ts

@@ -12,23 +12,34 @@ import { INodeTransformer } from '../../../../src/interfaces/node-transformers/I
 
 import { NodeTransformer } from '../../../../src/enums/node-transformers/NodeTransformer';
 import { NodeFactory } from '../../../../src/node/NodeFactory';
-import { NodeUtils } from '../../../../src/node/NodeUtils';
 import { NodeMetadata } from '../../../../src/node/NodeMetadata';
+import { NodeUtils } from '../../../../src/node/NodeUtils';
 
 describe('ObfuscatingGuardsTransformer', () => {
     describe('transformNode', () => {
+        const forceTransformString: string = 'important string';
+        const ignoredAndForceTransformString: string = 'important ignored string';
+
         let inversifyContainerFacade: IInversifyContainerFacade,
             obfuscatingGuardsTransformer: INodeTransformer;
 
         before(() => {
             inversifyContainerFacade = new InversifyContainerFacade();
-            inversifyContainerFacade.load('', '', {});
+            inversifyContainerFacade.load('', '', {
+                forceTransformStrings: [
+                    forceTransformString,
+                    ignoredAndForceTransformString
+                ],
+                reservedStrings: [
+                    ignoredAndForceTransformString
+                ]
+            });
 
             obfuscatingGuardsTransformer = inversifyContainerFacade
                 .getNamed(ServiceIdentifiers.INodeTransformer, NodeTransformer.ObfuscatingGuardsTransformer);
         });
 
-        describe('Variant #1: valid node', () => {
+        describe('Variant #1: transform node', () => {
             const identifier: ESTree.Identifier = NodeFactory.identifierNode('foo');
 
             const expectedResult: ESTree.Identifier = NodeUtils.clone(identifier);
@@ -38,7 +49,10 @@ describe('ObfuscatingGuardsTransformer', () => {
             before(() => {
                 identifier.parentNode = identifier;
 
-                NodeMetadata.set(expectedResult, { ignoredNode: false });
+                NodeMetadata.set(expectedResult, {
+                    forceTransformNode: false,
+                    ignoredNode: false
+                });
 
                 result = <ESTree.Identifier>obfuscatingGuardsTransformer.transformNode(identifier, identifier);
             });
@@ -48,7 +62,7 @@ describe('ObfuscatingGuardsTransformer', () => {
             });
         });
 
-        describe('Variant #2: invalid node', () => {
+        describe('Variant #2: ignored node', () => {
             const expressionStatement: ESTree.ExpressionStatement = NodeFactory.directiveNode(
                 NodeFactory.literalNode('use strict'),
                 'use strict'
@@ -63,7 +77,10 @@ describe('ObfuscatingGuardsTransformer', () => {
                 expressionStatement.expression.parentNode = expressionStatement;
 
                 expectedResult.parentNode = expectedResult;
-                NodeMetadata.set(expectedResult, { ignoredNode: true });
+                NodeMetadata.set(expectedResult, {
+                    forceTransformNode: false,
+                    ignoredNode: true
+                });
 
                 result = <ESTree.ExpressionStatement>obfuscatingGuardsTransformer
                     .transformNode(expressionStatement, expressionStatement);
@@ -73,5 +90,55 @@ describe('ObfuscatingGuardsTransformer', () => {
                 assert.deepEqual(result, expectedResult);
             });
         });
+
+        describe('Variant #3: force transform node', () => {
+            const literalNode: ESTree.Literal =  NodeFactory.literalNode(forceTransformString);
+
+            const expectedResult: ESTree.Literal = NodeUtils.clone(literalNode);
+
+            let result: ESTree.Literal;
+
+            before(() => {
+                literalNode.parentNode = literalNode;
+
+                expectedResult.parentNode = expectedResult;
+                NodeMetadata.set(expectedResult, {
+                    forceTransformNode: true,
+                    ignoredNode: false
+                });
+
+                result = <ESTree.Literal>obfuscatingGuardsTransformer
+                    .transformNode(literalNode, literalNode);
+            });
+
+            it('should add `forceTransformNode` property with `true` value to given node', () => {
+                assert.deepEqual(result, expectedResult);
+            });
+        });
+
+        describe('Variant #4: ignored node and force transform node', () => {
+            const literalNode: ESTree.Literal = NodeFactory.literalNode(ignoredAndForceTransformString);
+
+            const expectedResult: ESTree.Literal = NodeUtils.clone(literalNode);
+
+            let result: ESTree.Literal;
+
+            before(() => {
+                literalNode.parentNode = literalNode;
+
+                expectedResult.parentNode = expectedResult;
+                NodeMetadata.set(expectedResult, {
+                    forceTransformNode: true,
+                    ignoredNode: false
+                });
+
+                result = <ESTree.Literal>obfuscatingGuardsTransformer
+                    .transformNode(literalNode, literalNode);
+            });
+
+            it('should add correct metadata to given node', () => {
+                assert.deepEqual(result, expectedResult);
+            });
+        });
     });
 });

+ 44 - 0
test/unit-tests/node/node-literal-utils/NodeLiteralUtils.spec.ts

@@ -6,6 +6,50 @@ import { NodeFactory } from '../../../../src/node/NodeFactory';
 import { NodeLiteralUtils } from '../../../../src/node/NodeLiteralUtils';
 
 describe('NodeLiteralUtils', () => {
+    describe('isStringLiteralNode', () => {
+        describe('Variant #1: string literal node', () => {
+            let result: boolean;
+
+            before(() => {
+                const literalNode: ESTree.Literal = NodeFactory.literalNode('foo');
+
+                result = NodeLiteralUtils.isStringLiteralNode(literalNode);
+            });
+
+            it('should return `true` for string literal node', () => {
+                assert.isTrue(result);
+            });
+        });
+
+        describe('Variant #2: number literal node', () => {
+            let result: boolean;
+
+            before(() => {
+                const literalNode: ESTree.Literal = NodeFactory.literalNode(123);
+
+                result = NodeLiteralUtils.isStringLiteralNode(literalNode);
+            });
+
+            it('should return `false` for number literal node', () => {
+                assert.isFalse(result);
+            });
+        });
+
+        describe('Variant #3: boolean literal node', () => {
+            let result: boolean;
+
+            before(() => {
+                const literalNode: ESTree.Literal = NodeFactory.literalNode(true);
+
+                result = NodeLiteralUtils.isStringLiteralNode(literalNode);
+            });
+
+            it('should return `false` for boolean literal node', () => {
+                assert.isFalse(result);
+            });
+        });
+    });
+
     describe('isProhibitedLiteralNode', () => {
         describe('String literal node', () => {
             describe('Variant #1: base string literal node', () => {

+ 18 - 0
test/unit-tests/node/node-metadata/NodeMetadata.spec.ts

@@ -47,6 +47,24 @@ describe('NodeMetadata', () => {
         });
     });
 
+    describe('isForceTransformNode', () => {
+        const expectedValue: boolean = true;
+
+        let node: ESTree.Identifier,
+            value: boolean | undefined;
+
+        before(() => {
+            node = NodeFactory.identifierNode('foo');
+            node.metadata = {};
+            node.metadata.forceTransformNode = true;
+            value = NodeMetadata.isForceTransformNode(node);
+        });
+
+        it('should return metadata value', () => {
+            assert.equal(value, expectedValue);
+        });
+    });
+
     describe('isIgnoredNode', () => {
         const expectedValue: boolean = true;
 

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä