Jelajahi Sumber

`forceTransformedStrings` option

sanex 4 tahun lalu
induk
melakukan
a5d7f7418c
40 mengubah file dengan 648 tambahan dan 135 penghapusan
  1. 4 0
      CHANGELOG.md
  2. 19 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. 12 6
      src/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.ts
  8. 5 0
      src/cli/JavaScriptObfuscatorCLI.ts
  9. 10 4
      src/container/modules/node-transformers/PreparingTransformersModule.ts
  10. 1 0
      src/declarations/ESTree.d.ts
  11. 1 0
      src/enums/node-transformers/preparing-transformers/obfuscating-guards/ObfuscatingGuard.ts
  12. 5 0
      src/enums/node/ObfuscatingGuardResult.ts
  13. 2 2
      src/interfaces/node-transformers/preparing-transformers/obfuscating-guards/IObfuscatingGuard.ts
  14. 1 0
      src/interfaces/options/IOptions.ts
  15. 2 1
      src/node-transformers/control-flow-transformers/control-flow-replacers/StringLiteralControlFlowReplacer.ts
  16. 1 1
      src/node-transformers/converting-transformers/SplitStringTransformer.ts
  17. 32 6
      src/node-transformers/preparing-transformers/ObfuscatingGuardsTransformer.ts
  18. 8 8
      src/node-transformers/preparing-transformers/obfuscating-guards/BlackListObfuscatingGuard.ts
  19. 13 11
      src/node-transformers/preparing-transformers/obfuscating-guards/ConditionalCommentObfuscatingGuard.ts
  20. 58 0
      src/node-transformers/preparing-transformers/obfuscating-guards/ForceTransformedStringObfuscatingGuard.ts
  21. 9 5
      src/node-transformers/preparing-transformers/obfuscating-guards/ReservedStringObfuscatingGuard.ts
  22. 5 2
      src/node-transformers/string-array-transformers/StringArrayTransformer.ts
  23. 8 0
      src/node/NodeLiteralUtils.ts
  24. 8 0
      src/node/NodeMetadata.ts
  25. 10 0
      src/options/Options.ts
  26. 0 2
      src/options/OptionsNormalizer.ts
  27. 0 27
      src/options/normalizer-rules/StringArrayThresholdRule.ts
  28. 1 0
      src/options/presets/Default.ts
  29. 1 0
      src/options/presets/NoCustomNodes.ts
  30. 0 3
      src/types/node/TNodeGuard.ts
  31. 5 0
      src/types/node/TObfuscatingGuard.ts
  32. 2 1
      test/dev/dev.ts
  33. 185 4
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts
  34. 2 0
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/force-transformed-strings-option-1.js
  35. 0 28
      test/functional-tests/options/OptionsNormalizer.spec.ts
  36. 85 1
      test/unit-tests/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.spec.ts
  37. 17 16
      test/unit-tests/generators/identifier-names-generators/MangledlIdentifierNamesGenerator.spec.ts
  38. 73 6
      test/unit-tests/node-transformers/preparing-transformers/ObfuscatingGuardsTransformer.spec.ts
  39. 44 0
      test/unit-tests/node/node-literal-utils/NodeLiteralUtils.spec.ts
  40. 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:** `forceTransformedStrings` allows force transform strings even if by `stringArrayThreshold` (or possible other thresholds in the future) they shouldn't be transformed
+
 v2.3.1
 ---
 * Fixed a rare bug with `identifierNamesGenerator: 'mangled'` option that causes wrong identifier names generation

+ 19 - 0
README.md

@@ -339,6 +339,7 @@ Following options are available for the JS Obfuscator:
     debugProtectionInterval: false,
     disableConsoleOutput: false,
     domainLock: [],
+    forceTransformedStrings: [],
     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-transformed-strings '<list>' (comma separated)
     --identifier-names-generator <string> [dictionary, hexadecimal, mangled, mangled-shuffled]
     --identifiers-dictionary '<list>' (comma separated)
     --identifiers-prefix <string>
@@ -656,6 +658,23 @@ Type: `string[]` Default: `[]`
 
 A file names or globs which indicates files to exclude from obfuscation. 
 
+### `forceTransformedStrings`
+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)
+
+Example:
+```ts
+	{
+		forceTransformedStrings: [
+			'some-important-value',
+			'some-string_\d'
+		]
+	}
+```
+
 ### `identifierNamesGenerator`
 Type: `string` Default: `hexadecimal`
 

File diff ditekan karena terlalu besar
+ 0 - 0
dist/index.browser.js


File diff ditekan karena terlalu besar
+ 0 - 0
dist/index.cli.js


File diff ditekan karena terlalu besar
+ 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",

+ 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 isForceObfuscatedNode: boolean = NodeMetadata.isForceObfuscatedNode(literalNode);
+
+        if (isForceObfuscatedNode) {
+            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-transformed-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. ' +

+ 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 { ForceTransformedStringObfuscatingGuard } from '../../../node-transformers/preparing-transformers/obfuscating-guards/ForceTransformedStringObfuscatingGuard';
 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(ForceTransformedStringObfuscatingGuard)
+        .inSingletonScope()
+        .whenTargetNamed(ObfuscatingGuard.ForceTransformedStringObfuscatingGuard);
+
     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 - 0
src/declarations/ESTree.d.ts

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

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

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

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

@@ -0,0 +1,5 @@
+export enum ObfuscatingGuardResult {
+    ForceObfuscated = 'ForceObfuscated',
+    Ignored = 'Ignored',
+    Obfuscated = 'Obfuscated'
+}

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

+ 32 - 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.ForceTransformedStringObfuscatingGuard,
         ObfuscatingGuard.ReservedStringObfuscatingGuard
     ];
 
@@ -84,13 +86,37 @@ 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 {
+        let ignoredNode: boolean = false;
+        let forceObfuscatedNode: boolean = false;
+
+        for (const obfuscatingGuardResult of obfuscatingGuardResults) {
+            if (obfuscatingGuardResult === ObfuscatingGuardResult.Ignored) {
+                ignoredNode = true;
+                forceObfuscatedNode = false;
+                break;
+            }
+
+            if (obfuscatingGuardResult === ObfuscatingGuardResult.ForceObfuscated) {
+                forceObfuscatedNode = true;
+            }
+        }
+
+        NodeMetadata.set(node, {
+            ignoredNode: ignoredNode && !NodeGuards.isProgramNode(node),
+            forceObfuscatedNode: forceObfuscatedNode && !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.Ignored;
             }
         }
 
-        return true;
+        return ObfuscatingGuardResult.Obfuscated;
     }
 }

+ 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.Obfuscated
+            : ObfuscatingGuardResult.Ignored;
     }
 
     /**

+ 58 - 0
src/node-transformers/preparing-transformers/obfuscating-guards/ForceTransformedStringObfuscatingGuard.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 ForceTransformedStringObfuscatingGuard 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.forceTransformedStrings.length
+            && NodeGuards.isLiteralNode(node)
+            && typeof node.value === 'string'
+        ) {
+            return !this.isForceTransformedString(node.value)
+                ? ObfuscatingGuardResult.Obfuscated
+                : ObfuscatingGuardResult.ForceObfuscated;
+        }
+
+        return ObfuscatingGuardResult.Obfuscated;
+    }
+
+    /**
+     * @param {string} value
+     * @returns {boolean}
+     */
+    private isForceTransformedString (value: string): boolean {
+        return this.options.forceTransformedStrings
+            .some((forceTransformedString: string) => {
+                return new RegExp(forceTransformedString, '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.Obfuscated
+                : ObfuscatingGuardResult.Ignored;
         }
 
-        return true;
+        return ObfuscatingGuardResult.Obfuscated;
     }
 
     /**

+ 5 - 2
src/node-transformers/string-array-transformers/StringArrayTransformer.ts

@@ -191,7 +191,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;
         }
 
@@ -413,7 +416,7 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
         literalNode: ESTree.Literal,
         parentNode: ESTree.Node
     ): ESTree.Literal {
-        if (typeof literalNode.value !== 'string') {
+        if (!NodeLiteralUtils.isStringLiteralNode(literalNode)) {
             return literalNode;
         }
 

+ 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 isForceObfuscatedNode (node: ESTree.Node): boolean {
+        return NodeMetadata.get(node, 'forceObfuscatedNode') === 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 forceTransformedStrings!: 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: [],
+    forceTransformedStrings: [],
     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: [],
+    forceTransformedStrings: [],
     identifierNamesGenerator: IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator,
     identifiersPrefix: '',
     identifiersDictionary: [],

+ 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 - 1
test/dev/dev.ts

@@ -18,9 +18,10 @@ import { StringArrayWrappersType } from '../../src/enums/node-transformers/strin
         {
             ...NO_ADDITIONAL_NODES_PRESET,
             compact: false,
+            forceTransformedStrings: ['foo'],
             identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
             stringArray: true,
-            stringArrayThreshold: 1,
+            stringArrayThreshold: 0,
             stringArrayWrappersChainedCalls: true,
             stringArrayWrappersCount: 1,
             stringArrayWrappersType: StringArrayWrappersType.Function

+ 185 - 4
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts

@@ -689,7 +689,188 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #16: object expression key literal', () => {
+    describe('Variant #16: `forceTransformedStrings` option is enabled', () => {
+        describe('Variant #1: base `forceTransformedStrings` values', () => {
+            describe('Variant #1: single force transformed 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-transformed-strings-option-1.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            forceTransformedStrings: ['bar'],
+                            stringArray: true,
+                            stringArrayThreshold: 0
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('match #1: should not transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp1);
+                });
+
+                it('match #2: should transform force transformed string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp2);
+                });
+            });
+
+            describe('Variant #2: two force transformed 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-transformed-strings-option-1.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            forceTransformedStrings: ['foo', 'bar'],
+                            stringArray: true,
+                            stringArrayThreshold: 0
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('match #1: should transform force transformed string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp1);
+                });
+
+                it('match #2: should transform force transformed string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp2);
+                });
+            });
+        });
+
+        describe('Variant #2: RegExp `forceTransformedStrings` values', () => {
+            describe('Variant #1: single force transformed 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-transformed-strings-option-1.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            forceTransformedStrings: ['ar$'],
+                            stringArray: true,
+                            stringArrayThreshold: 0
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('match #1: should not transform string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp1);
+                });
+
+                it('match #2: should transform force transformed string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp2);
+                });
+            });
+
+            describe('Variant #2: two force transformed 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-transformed-strings-option-1.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            forceTransformedStrings: ['^fo', '.ar'],
+                            stringArray: true,
+                            stringArrayThreshold: 1
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('match #1: should transform force transformed string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp1);
+                });
+
+                it('match #2: should transform force transformed string', () => {
+                    assert.match(obfuscatedCode, stringLiteralRegExp2);
+                });
+            });
+        });
+
+        describe('Variant #3: `unicodeEscapeSequence` option is disabled', () => {
+            const stringLiteralRegExp1: RegExp = /const foo *= *'foo';/;
+            const stringLiteralRegExp2: RegExp = /const bar *= *'bar';/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/force-transformed-strings-option-1.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        forceTransformedStrings: ['bar'],
+                        unicodeEscapeSequence: false
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('match #1: should not encode force transformed string with unicode escape sequence', () => {
+                assert.match(obfuscatedCode, stringLiteralRegExp1);
+            });
+
+            it('match #2: should not encode force transformed string with unicode escape sequence', () => {
+                assert.match(obfuscatedCode, stringLiteralRegExp2);
+            });
+        });
+
+        describe('Variant #4: `stringArray` option is disabled', () => {
+            describe('Variant #1: base case', () => {
+                const stringLiteralRegExp1: RegExp = /const foo *= *'foo';/;
+                const stringLiteralRegExp2: RegExp = /const bar *= *'bar';/;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/force-transformed-strings-option-1.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            forceTransformedStrings: ['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 #17: 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 +928,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #17: import declaration source literal', () => {
+    describe('Variant #18: import declaration source literal', () => {
         const importDeclarationRegExp: RegExp = /import *{ *bar *} *from *'foo';/;
 
         let obfuscatedCode: string;
@@ -770,7 +951,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #18: export all declaration source literal', () => {
+    describe('Variant #19: export all declaration source literal', () => {
         const exportAllDeclarationRegExp: RegExp = /export *\* *from *'foo';/;
 
         let obfuscatedCode: string;
@@ -793,7 +974,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #19: export named declaration source literal', () => {
+    describe('Variant #20: export named declaration source literal', () => {
         const exportNamedDeclarationRegExp: RegExp = /export *{ *bar *} *from *'foo';/;
 
         let obfuscatedCode: string;

+ 2 - 0
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/force-transformed-strings-option-1.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({

+ 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 obfuscated 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, {forceObfuscatedNode: 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, {forceObfuscatedNode: 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 forceObfuscatedString: string = 'important string';
+        const ignoredAndForceObfuscatedString: string = 'important ignored string';
+
         let inversifyContainerFacade: IInversifyContainerFacade,
             obfuscatingGuardsTransformer: INodeTransformer;
 
         before(() => {
             inversifyContainerFacade = new InversifyContainerFacade();
-            inversifyContainerFacade.load('', '', {});
+            inversifyContainerFacade.load('', '', {
+                forceTransformedStrings: [
+                    forceObfuscatedString,
+                    ignoredAndForceObfuscatedString
+                ],
+                reservedStrings: [
+                    ignoredAndForceObfuscatedString
+                ]
+            });
 
             obfuscatingGuardsTransformer = inversifyContainerFacade
                 .getNamed(ServiceIdentifiers.INodeTransformer, NodeTransformer.ObfuscatingGuardsTransformer);
         });
 
-        describe('Variant #1: valid node', () => {
+        describe('Variant #1: allowed 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, {
+                    forceObfuscatedNode: 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, {
+                    forceObfuscatedNode: false,
+                    ignoredNode: true
+                });
 
                 result = <ESTree.ExpressionStatement>obfuscatingGuardsTransformer
                     .transformNode(expressionStatement, expressionStatement);
@@ -73,5 +90,55 @@ describe('ObfuscatingGuardsTransformer', () => {
                 assert.deepEqual(result, expectedResult);
             });
         });
+
+        describe('Variant #3: force obfuscated node', () => {
+            const literalNode: ESTree.Literal =  NodeFactory.literalNode(forceObfuscatedString);
+
+            const expectedResult: ESTree.Literal = NodeUtils.clone(literalNode);
+
+            let result: ESTree.Literal;
+
+            before(() => {
+                literalNode.parentNode = literalNode;
+
+                expectedResult.parentNode = expectedResult;
+                NodeMetadata.set(expectedResult, {
+                    forceObfuscatedNode: true,
+                    ignoredNode: false
+                });
+
+                result = <ESTree.Literal>obfuscatingGuardsTransformer
+                    .transformNode(literalNode, literalNode);
+            });
+
+            it('should add `forceObfuscatedNode` property with `true` value to given node', () => {
+                assert.deepEqual(result, expectedResult);
+            });
+        });
+
+        describe('Variant #4: ignored node and force obfuscated node', () => {
+            const literalNode: ESTree.Literal = NodeFactory.literalNode(ignoredAndForceObfuscatedString);
+
+            const expectedResult: ESTree.Literal = NodeUtils.clone(literalNode);
+
+            let result: ESTree.Literal;
+
+            before(() => {
+                literalNode.parentNode = literalNode;
+
+                expectedResult.parentNode = expectedResult;
+                NodeMetadata.set(expectedResult, {
+                    forceObfuscatedNode: false,
+                    ignoredNode: true
+                });
+
+                result = <ESTree.Literal>obfuscatingGuardsTransformer
+                    .transformNode(literalNode, literalNode);
+            });
+
+            it('should add `ignoredNode` property with `true` value 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('isForceObfuscatedNode', () => {
+        const expectedValue: boolean = true;
+
+        let node: ESTree.Identifier,
+            value: boolean | undefined;
+
+        before(() => {
+            node = NodeFactory.identifierNode('foo');
+            node.metadata = {};
+            node.metadata.forceObfuscatedNode = true;
+            value = NodeMetadata.isForceObfuscatedNode(node);
+        });
+
+        it('should return metadata value', () => {
+            assert.equal(value, expectedValue);
+        });
+    });
+
     describe('isIgnoredNode', () => {
         const expectedValue: boolean = true;
 

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini