Przeglądaj źródła

Code transformers #2: HashbangOperatorTransformer

sanex3339 5 lat temu
rodzic
commit
e7a6186eb6

Plik diff jest za duży
+ 0 - 0
dist/index.browser.js


Plik diff jest za duży
+ 0 - 0
dist/index.cli.js


Plik diff jest za duży
+ 0 - 0
dist/index.js


+ 55 - 16
src/JavaScriptObfuscator.ts

@@ -7,6 +7,7 @@ import * as ESTree from 'estree';
 
 import { TObfuscatedCodeFactory } from './types/container/source-code/TObfuscatedCodeFactory';
 
+import { ICodeTransformersRunner } from './interfaces/code-transformers/ICodeTransformersRunner';
 import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
 import { IJavaScriptObfuscator } from './interfaces/IJavaScriptObfsucator';
 import { ILogger } from './interfaces/logger/ILogger';
@@ -15,6 +16,8 @@ import { IOptions } from './interfaces/options/IOptions';
 import { IRandomGenerator } from './interfaces/utils/IRandomGenerator';
 import { INodeTransformersRunner } from './interfaces/node-transformers/INodeTransformersRunner';
 
+import { CodeTransformer } from './enums/code-transformers/CodeTransformer';
+import { CodeTransformationStage } from './enums/code-transformers/CodeTransformationStage';
 import { LoggingMessage } from './enums/logger/LoggingMessage';
 import { NodeTransformer } from './enums/node-transformers/NodeTransformer';
 import { NodeTransformationStage } from './enums/node-transformers/NodeTransformationStage';
@@ -48,10 +51,17 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         sourceMapWithCode: true
     };
 
+    /**
+     * @type {CodeTransformer[]}
+     */
+    private static readonly codeTransformersList: CodeTransformer[] = [
+        CodeTransformer.HashbangOperatorTransformer
+    ];
+
     /**
      * @type {NodeTransformer[]}
      */
-    private static readonly transformersList: NodeTransformer[] = [
+    private static readonly nodeTransformersList: NodeTransformer[] = [
         NodeTransformer.BlockStatementControlFlowTransformer,
         NodeTransformer.CommentsTransformer,
         NodeTransformer.CustomCodeHelpersTransformer,
@@ -73,6 +83,11 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         NodeTransformer.VariablePreserveTransformer
     ];
 
+    /**
+     * @type {ICodeTransformersRunner}
+     */
+    private readonly codeTransformersRunner: ICodeTransformersRunner;
+
     /**
      * @type {ILogger}
      */
@@ -96,23 +111,26 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
     /**
      * @type {INodeTransformersRunner}
      */
-    private readonly transformersRunner: INodeTransformersRunner;
+    private readonly nodeTransformersRunner: INodeTransformersRunner;
 
     /**
-     * @param {INodeTransformersRunner} transformersRunner
+     * @param {ICodeTransformersRunner} codeTransformersRunner
+     * @param {INodeTransformersRunner} nodeTransformersRunner
      * @param {IRandomGenerator} randomGenerator
      * @param {TObfuscatedCodeFactory} obfuscatedCodeFactory
      * @param {ILogger} logger
      * @param {IOptions} options
      */
     public constructor (
-        @inject(ServiceIdentifiers.INodeTransformersRunner) transformersRunner: INodeTransformersRunner,
+        @inject(ServiceIdentifiers.ICodeTransformersRunner) codeTransformersRunner: ICodeTransformersRunner,
+        @inject(ServiceIdentifiers.INodeTransformersRunner) nodeTransformersRunner: INodeTransformersRunner,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
         @inject(ServiceIdentifiers.Factory__IObfuscatedCode) obfuscatedCodeFactory: TObfuscatedCodeFactory,
         @inject(ServiceIdentifiers.ILogger) logger: ILogger,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
-        this.transformersRunner = transformersRunner;
+        this.codeTransformersRunner = codeTransformersRunner;
+        this.nodeTransformersRunner = nodeTransformersRunner;
         this.randomGenerator = randomGenerator;
         this.obfuscatedCodeFactory = obfuscatedCodeFactory;
         this.logger = logger;
@@ -129,6 +147,9 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         this.logger.info(LoggingMessage.ObfuscationStarted);
         this.logger.info(LoggingMessage.RandomGeneratorSeed, this.randomGenerator.getInputSeed());
 
+        // preparing code transformations
+        sourceCode = this.runCodeTransformationStage(sourceCode, CodeTransformationStage.PreparingTransformers);
+
         // parse AST tree
         const astTree: ESTree.Program = this.parseCode(sourceCode);
 
@@ -138,6 +159,9 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         // generate code
         const generatorOutput: IGeneratorOutput = this.generateCode(sourceCode, obfuscatedAstTree);
 
+        // finalizing code transformations
+        generatorOutput.code = this.runCodeTransformationStage(generatorOutput.code, CodeTransformationStage.FinalizingTransformers);
+
         const obfuscationTime: number = (Date.now() - timeStart) / 1000;
         this.logger.success(LoggingMessage.ObfuscationCompleted, obfuscationTime);
 
@@ -157,7 +181,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
      * @returns {Program}
      */
     private transformAstTree (astTree: ESTree.Program): ESTree.Program {
-        astTree = this.runTransformationStage(astTree, NodeTransformationStage.Initializing);
+        astTree = this.runNodeTransformationStage(astTree, NodeTransformationStage.Initializing);
 
         const isEmptyAstTree: boolean = NodeGuards.isProgramNode(astTree)
             && !astTree.body.length
@@ -170,19 +194,19 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
             return astTree;
         }
 
-        astTree = this.runTransformationStage(astTree, NodeTransformationStage.Preparing);
+        astTree = this.runNodeTransformationStage(astTree, NodeTransformationStage.Preparing);
 
         if (this.options.deadCodeInjection) {
-            astTree = this.runTransformationStage(astTree, NodeTransformationStage.DeadCodeInjection);
+            astTree = this.runNodeTransformationStage(astTree, NodeTransformationStage.DeadCodeInjection);
         }
 
         if (this.options.controlFlowFlattening) {
-            astTree = this.runTransformationStage(astTree, NodeTransformationStage.ControlFlowFlattening);
+            astTree = this.runNodeTransformationStage(astTree, NodeTransformationStage.ControlFlowFlattening);
         }
 
-        astTree = this.runTransformationStage(astTree, NodeTransformationStage.Converting);
-        astTree = this.runTransformationStage(astTree, NodeTransformationStage.Obfuscating);
-        astTree = this.runTransformationStage(astTree, NodeTransformationStage.Finalizing);
+        astTree = this.runNodeTransformationStage(astTree, NodeTransformationStage.Converting);
+        astTree = this.runNodeTransformationStage(astTree, NodeTransformationStage.Obfuscating);
+        astTree = this.runNodeTransformationStage(astTree, NodeTransformationStage.Finalizing);
 
         return astTree;
     }
@@ -222,17 +246,32 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         return this.obfuscatedCodeFactory(generatorOutput.code, generatorOutput.map);
     }
 
+    /**
+     * @param {string} code
+     * @param {CodeTransformationStage} codeTransformationStage
+     * @returns {string}
+     */
+    private runCodeTransformationStage (code: string, codeTransformationStage: CodeTransformationStage): string {
+        this.logger.info(LoggingMessage.CodeTransformationStage, codeTransformationStage);
+
+        return this.codeTransformersRunner.transform(
+            code,
+            JavaScriptObfuscator.codeTransformersList,
+            codeTransformationStage
+        );
+    }
+
     /**
      * @param {Program} astTree
      * @param {NodeTransformationStage} nodeTransformationStage
      * @returns {Program}
      */
-    private runTransformationStage (astTree: ESTree.Program, nodeTransformationStage: NodeTransformationStage): ESTree.Program {
-        this.logger.info(LoggingMessage.TransformationStage, nodeTransformationStage);
+    private runNodeTransformationStage (astTree: ESTree.Program, nodeTransformationStage: NodeTransformationStage): ESTree.Program {
+        this.logger.info(LoggingMessage.NodeTransformationStage, nodeTransformationStage);
 
-        return this.transformersRunner.transform(
+        return this.nodeTransformersRunner.transform(
             astTree,
-            JavaScriptObfuscator.transformersList,
+            JavaScriptObfuscator.nodeTransformersList,
             nodeTransformationStage
         );
     }

+ 5 - 5
src/code-transformers/AbstractCodeTransformer.ts

@@ -10,6 +10,11 @@ import { CodeTransformationStage } from '../enums/code-transformers/CodeTransfor
 
 @injectable()
 export abstract class AbstractCodeTransformer implements ICodeTransformer {
+    /**
+     * @type {CodeTransformer[]}
+     */
+    public readonly runAfter: CodeTransformer[] | undefined;
+
     /**
      * @type {IOptions}
      */
@@ -20,11 +25,6 @@ export abstract class AbstractCodeTransformer implements ICodeTransformer {
      */
     protected readonly randomGenerator: IRandomGenerator;
 
-    /**
-     * @type {CodeTransformer[]}
-     */
-    public readonly abstract runAfter: CodeTransformer[];
-
     /**
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options

+ 100 - 0
src/code-transformers/CodeTransformersRunner.ts

@@ -0,0 +1,100 @@
+import { inject, injectable } from 'inversify';
+
+import { ServiceIdentifiers } from '../container/ServiceIdentifiers';
+
+import { TCodeTransformerFactory } from '../types/container/code-transformers/TCodeTransformerFactory';
+import { TObject } from '../types/TObject';
+
+import { ICodeTransformer } from '../interfaces/code-transformers/ICodeTransformer';
+import { ICodeTransformersRunner } from '../interfaces/code-transformers/ICodeTransformersRunner';
+import { ITransformerNamesGroupsBuilder } from '../interfaces/utils/ITransformerNamesGroupsBuilder';
+
+import { CodeTransformer } from '../enums/code-transformers/CodeTransformer';
+import { CodeTransformationStage } from '../enums/code-transformers/CodeTransformationStage';
+
+@injectable()
+export class CodeTransformersRunner implements ICodeTransformersRunner {
+    /**
+     * @type {TCodeTransformerFactory}
+     */
+    private readonly codeTransformerFactory: TCodeTransformerFactory;
+
+    /**
+     * @type {ITransformerNamesGroupsBuilder}
+     */
+    private readonly codeTransformerNamesGroupsBuilder: ITransformerNamesGroupsBuilder<
+        CodeTransformer,
+        ICodeTransformer
+    >;
+
+    /**
+     * @param {TNodeTransformerFactory} codeTransformerFactory
+     * @param {ITransformerNamesGroupsBuilder} codeTransformerNamesGroupsBuilder
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.Factory__ICodeTransformer)
+            codeTransformerFactory: TCodeTransformerFactory,
+        @inject(ServiceIdentifiers.ICodeTransformerNamesGroupsBuilder)
+            codeTransformerNamesGroupsBuilder: ITransformerNamesGroupsBuilder<
+                CodeTransformer,
+                ICodeTransformer
+            >,
+    ) {
+        this.codeTransformerFactory = codeTransformerFactory;
+        this.codeTransformerNamesGroupsBuilder = codeTransformerNamesGroupsBuilder;
+    }
+
+    /**
+     * @param {string} code
+     * @param {CodeTransformer[]} codeTransformerNames
+     * @param {CodeTransformationStage} codeTransformationStage
+     * @returns {string}
+     */
+    public transform (
+        code: string,
+        codeTransformerNames: CodeTransformer[],
+        codeTransformationStage: CodeTransformationStage
+    ): string {
+        if (!codeTransformerNames.length) {
+            return code;
+        }
+
+        const normalizedCodeTransformers: TObject<ICodeTransformer> =
+            this.buildNormalizedCodeTransformers(codeTransformerNames, codeTransformationStage);
+        const codeTransformerNamesGroups: CodeTransformer[][] =
+            this.codeTransformerNamesGroupsBuilder.build(normalizedCodeTransformers);
+
+        for (const nodeTransformerNamesGroup of codeTransformerNamesGroups) {
+            for (const nodeTransformerName of nodeTransformerNamesGroup) {
+                const codeTransformer: ICodeTransformer = normalizedCodeTransformers[nodeTransformerName];
+
+                code = codeTransformer.transformCode(code, codeTransformationStage);
+            }
+        }
+
+        return code;
+    }
+
+    /**
+     * @param {NodeTransformer[]} codeTransformerNames
+     * @param {NodeTransformationStage} codeTransformationStage
+     * @returns {TObject<INodeTransformer>}
+     */
+    private buildNormalizedCodeTransformers (
+        codeTransformerNames: CodeTransformer[],
+        codeTransformationStage: CodeTransformationStage
+    ): TObject<ICodeTransformer> {
+        return codeTransformerNames
+            .reduce<TObject<ICodeTransformer>>(
+                (acc: TObject<ICodeTransformer>, codeTransformerName: CodeTransformer) => {
+                    const codeTransformer: ICodeTransformer = this.codeTransformerFactory(codeTransformerName);
+
+                    return {
+                        ...acc,
+                        [codeTransformerName]: codeTransformer
+                    };
+                },
+                {}
+            );
+    }
+}

+ 72 - 0
src/code-transformers/preparing-transformers/HashbangOperatorTransformer.ts

@@ -0,0 +1,72 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+
+import { CodeTransformationStage } from '../../enums/code-transformers/CodeTransformationStage';
+
+import { AbstractCodeTransformer } from '../AbstractCodeTransformer';
+
+@injectable()
+export class HashbangOperatorTransformer extends AbstractCodeTransformer {
+    /**
+     * @type {string | null}
+     */
+    private hashbangOperatorLine: string | null = null;
+
+    /**
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(randomGenerator, options);
+    }
+
+    /**
+     * Removes hashbang operator before AST transformation and appends it back after
+     *
+     * @param {string} code
+     * @param {CodeTransformationStage} codeTransformationStage
+     * @returns {string}
+     */
+    public transformCode (code: string, codeTransformationStage: CodeTransformationStage): string {
+        switch (codeTransformationStage) {
+            case CodeTransformationStage.PreparingTransformers:
+                return this.removeAndSaveHashbangOperatorLine(code);
+
+            case CodeTransformationStage.FinalizingTransformers:
+                return this.appendSavedHashbangOperatorLine(code);
+
+            default:
+                return code;
+        }
+    }
+
+    /**
+     * @param {string} code
+     * @returns {string}
+     */
+    private removeAndSaveHashbangOperatorLine (code: string): string {
+        return code
+            .replace(/^#!.*$(\r?\n)*/m, (substring: string) => {
+                if (substring) {
+                    this.hashbangOperatorLine = substring;
+                }
+
+                return '';
+            })
+            .trim();
+    }
+
+    /**
+     * @param {string} code
+     * @returns {string}
+     */
+    private appendSavedHashbangOperatorLine (code: string): string {
+        return `${this.hashbangOperatorLine ?? ''}${code}`;
+    }
+}

+ 8 - 1
src/container/InversifyContainerFacade.ts

@@ -20,6 +20,7 @@ import { utilsModule } from './modules/utils/UtilsModule';
 
 import { TInputOptions } from '../types/options/TInputOptions';
 
+import { ICodeTransformersRunner } from '../interfaces/code-transformers/ICodeTransformersRunner';
 import { IInversifyContainerFacade } from '../interfaces/container/IInversifyContainerFacade';
 import { IJavaScriptObfuscator } from '../interfaces/IJavaScriptObfsucator';
 import { ILogger } from '../interfaces/logger/ILogger';
@@ -28,12 +29,13 @@ import { IObfuscatedCode } from '../interfaces/source-code/IObfuscatedCode';
 import { ISourceCode } from '../interfaces/source-code/ISourceCode';
 import { INodeTransformersRunner } from '../interfaces/node-transformers/INodeTransformersRunner';
 
+import { CodeTransformersRunner } from '../code-transformers/CodeTransformersRunner';
 import { JavaScriptObfuscator } from '../JavaScriptObfuscator';
 import { Logger } from '../logger/Logger';
+import { NodeTransformersRunner } from '../node-transformers/NodeTransformersRunner';
 import { ObfuscationEventEmitter } from '../event-emitters/ObfuscationEventEmitter';
 import { ObfuscatedCode } from '../source-code/ObfuscatedCode';
 import { SourceCode } from '../source-code/SourceCode';
-import { NodeTransformersRunner } from '../node-transformers/NodeTransformersRunner';
 
 export class InversifyContainerFacade implements IInversifyContainerFacade {
     /**
@@ -166,6 +168,11 @@ export class InversifyContainerFacade implements IInversifyContainerFacade {
             .to(JavaScriptObfuscator)
             .inSingletonScope();
 
+        this.container
+            .bind<ICodeTransformersRunner>(ServiceIdentifiers.ICodeTransformersRunner)
+            .to(CodeTransformersRunner)
+            .inSingletonScope();
+
         this.container
             .bind<INodeTransformersRunner>(ServiceIdentifiers.INodeTransformersRunner)
             .to(NodeTransformersRunner)

+ 1 - 0
src/container/ServiceIdentifiers.ts

@@ -20,6 +20,7 @@ export enum ServiceIdentifiers {
     ICallsGraphAnalyzer = 'ICallsGraphAnalyzer',
     ICodeTransformer = 'ICodeTransformer',
     ICodeTransformerNamesGroupsBuilder = 'ICodeTransformerNamesGroupsBuilder',
+    ICodeTransformersRunner = 'ICodeTransformersRunner',
     ICryptUtils = 'ICryptUtils',
     ICustomCodeHelper = 'ICustomCodeHelper',
     ICustomCodeHelperGroup = 'ICustomCodeHelperGroup',

+ 6 - 0
src/container/modules/code-transformers/CodeTransformersModule.ts

@@ -8,6 +8,7 @@ import { ITransformerNamesGroupsBuilder } from '../../../interfaces/utils/ITrans
 import { CodeTransformer } from '../../../enums/code-transformers/CodeTransformer';
 
 import { CodeTransformerNamesGroupsBuilder } from '../../../code-transformers/CodeTransformerNamesGroupsBuilder';
+import { HashbangOperatorTransformer } from '../../../code-transformers/preparing-transformers/HashbangOperatorTransformer';
 
 export const codeTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
     // code transformers factory
@@ -19,4 +20,9 @@ export const codeTransformersModule: interfaces.ContainerModule = new ContainerM
     bind<ITransformerNamesGroupsBuilder<CodeTransformer, ICodeTransformer>>(ServiceIdentifiers.ICodeTransformerNamesGroupsBuilder)
         .to(CodeTransformerNamesGroupsBuilder)
         .inSingletonScope();
+
+    // preparing code transformers
+    bind<ICodeTransformer>(ServiceIdentifiers.ICodeTransformer)
+        .to(HashbangOperatorTransformer)
+        .whenTargetNamed(CodeTransformer.HashbangOperatorTransformer);
 });

+ 2 - 2
src/enums/code-transformers/CodeTransformationStage.ts

@@ -1,4 +1,4 @@
 export enum CodeTransformationStage {
-    BeforeNodeTransformers = 'BeforeNodeTransformers',
-    AfterNodeTransformers = 'AfterNodeTransformers',
+    PreparingTransformers = 'PreparingTransformers',
+    FinalizingTransformers = 'FinalizingTransformers',
 }

+ 2 - 1
src/enums/logger/LoggingMessage.ts

@@ -3,6 +3,7 @@ export enum LoggingMessage {
     ObfuscationCompleted = 'Obfuscation completed. Total time: %s sec.',
     ObfuscationStarted = 'Obfuscation started...',
     RandomGeneratorSeed = 'Random generator seed: %s...',
-    TransformationStage = 'Transformation stage: %s...',
+    CodeTransformationStage = 'Code transformation stage: %s...',
+    NodeTransformationStage = 'AST transformation stage: %s...',
     Version = 'Version: %s'
 }

+ 16 - 0
src/interfaces/code-transformers/ICodeTransformersRunner.ts

@@ -0,0 +1,16 @@
+import { CodeTransformer } from '../../enums/code-transformers/CodeTransformer';
+import { CodeTransformationStage } from '../../enums/code-transformers/CodeTransformationStage';
+
+export interface ICodeTransformersRunner {
+    /**
+     * @param {string} code
+     * @param {CodeTransformer[]} codeTransformers
+     * @param {CodeTransformationStage} codeTransformationStage
+     * @returns {string}
+     */
+    transform (
+        code: string,
+        codeTransformers: CodeTransformer[],
+        codeTransformationStage: CodeTransformationStage
+    ): string;
+}

+ 5 - 0
src/types/container/code-transformers/TCodeTransformerFactory.ts

@@ -0,0 +1,5 @@
+import { ICodeTransformer } from '../../../interfaces/code-transformers/ICodeTransformer';
+
+import { CodeTransformer } from '../../../enums/code-transformers/CodeTransformer';
+
+export type TCodeTransformerFactory = (codeTransformerName: CodeTransformer) => ICodeTransformer;

+ 3 - 1
test/dev/dev.ts

@@ -7,7 +7,9 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
-             /**
+            #!/usr/bin/env node
+        
+            /**
              * @license
              */
             var foo = 'abc';

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

@@ -0,0 +1,83 @@
+import { assert } from 'chai';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+
+import { readFileAsString } from '../../../../helpers/readFileAsString';
+
+import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
+
+describe('HashbangOperatorTransformer', () => {
+    describe('Variant #1: simple', () => {
+        const regExp: RegExp = new RegExp(
+            `#!\/usr\/bin\/env node\n` +
+            `var foo *= *'abc';`
+        );
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('should remove hashbang operator before ast transformation and append it after', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
+    });
+
+    describe('Variant #2: multiple new lines', () => {
+        const regExp: RegExp = new RegExp(
+            `#!\/usr\/bin\/env node\n\n\n\n` +
+            `var foo *= *'abc';`
+        );
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/multiple-new-lines.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('should remove hashbang operator before ast transformation and append it after', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
+    });
+
+    describe('Variant #3: `stringArray` option enabled', () => {
+        const regExp: RegExp = new RegExp(
+            `#!\/usr\/bin\/env node\n` +
+            `var _0x(\\w){4} *= *\\['abc'];`
+        );
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    stringArray: true,
+                    stringArrayThreshold: 1
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('should remove hashbang operator before ast transformation and append it after', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
+    });
+});

+ 5 - 0
test/functional-tests/code-transformers/preparing-transformers/hashbang-operator-transformer/fixtures/multiple-new-lines.js

@@ -0,0 +1,5 @@
+#!/usr/bin/env node
+
+
+
+var foo = 'abc';

+ 2 - 0
test/functional-tests/code-transformers/preparing-transformers/hashbang-operator-transformer/fixtures/simple.js

@@ -0,0 +1,2 @@
+#!/usr/bin/env node
+var foo = 'abc';

+ 1 - 0
test/index.spec.ts

@@ -53,6 +53,7 @@ import './unit-tests/utils/Utils.spec';
  */
 import './functional-tests/analyzers/calls-graph-analyzer/CallsGraphAnalyzer.spec';
 import './functional-tests/cli/JavaScriptObfuscatorCLI.spec';
+import './functional-tests/code-transformers/preparing-transformers/hashbang-operator-transformer/HashbangOperatorTransformer.spec';
 import './functional-tests/custom-code-helpers/console-output/ConsoleOutputDisableExpressionCodeHelper.spec';
 import './functional-tests/custom-code-helpers/common/templates/GlobalVariableNoEvalTemplate.spec';
 import './functional-tests/custom-code-helpers/debug-protection/templates/DebugProtectionFunctionCallTemplate.spec';

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików