Sfoglia il codice sorgente

Added `numbersToExpressions` option and NumberToNumericalExpressionTransformer

sanex3339 4 anni fa
parent
commit
dd8dcac223

+ 4 - 0
CHANGELOG.md

@@ -1,5 +1,9 @@
 Change Log
 Change Log
 
 
+v1.6.0
+---
+* **New option:** `numbersToExpressions` enables numbers conversion to expressions
+
 v1.5.2
 v1.5.2
 ---
 ---
 * Prevented mutation of the name sequences of `mangled` identifier name generators
 * Prevented mutation of the name sequences of `mangled` identifier name generators

+ 19 - 0
README.md

@@ -329,6 +329,7 @@ Following options are available for the JS Obfuscator:
     identifiersPrefix: '',
     identifiersPrefix: '',
     inputFileName: '',
     inputFileName: '',
     log: false,
     log: false,
+    numbersToExpressions: false,
     renameGlobals: false,
     renameGlobals: false,
     renameProperties: false,
     renameProperties: false,
     reservedNames: [],
     reservedNames: [],
@@ -375,6 +376,7 @@ Following options are available for the JS Obfuscator:
     --identifiers-dictionary '<list>' (comma separated)
     --identifiers-dictionary '<list>' (comma separated)
     --identifiers-prefix <string>
     --identifiers-prefix <string>
     --log <boolean>
     --log <boolean>
+    --numbers-to-expressions <boolean>
     --rename-globals <boolean>
     --rename-globals <boolean>
     --rename-properties <boolean>
     --rename-properties <boolean>
     --reserved-names '<list>' (comma separated)
     --reserved-names '<list>' (comma separated)
@@ -667,6 +669,20 @@ Type: `boolean` Default: `false`
 
 
 Enables logging of the information to the console.
 Enables logging of the information to the console.
 
 
+### `numbersToExpressions`
+Type: `boolean` Default: `false`
+
+Enables numbers conversion to expressions
+
+Example: 
+```ts
+// input
+const foo = 1234;
+
+// output
+const foo=-0xd93+-0x10b4+0x41*0x67+0x84e*0x3+-0xff8;
+```
+
 ### `renameGlobals`
 ### `renameGlobals`
 Type: `boolean` Default: `false`
 Type: `boolean` Default: `false`
 
 
@@ -985,6 +1001,7 @@ Performance will 50-100% slower than without obfuscation
     disableConsoleOutput: true,
     disableConsoleOutput: true,
     identifierNamesGenerator: 'hexadecimal',
     identifierNamesGenerator: 'hexadecimal',
     log: false,
     log: false,
+    numbersToExpressions: true,
     renameGlobals: false,
     renameGlobals: false,
     rotateStringArray: true,
     rotateStringArray: true,
     selfDefending: true,
     selfDefending: true,
@@ -1016,6 +1033,7 @@ Performance will 30-35% slower than without obfuscation
     disableConsoleOutput: true,
     disableConsoleOutput: true,
     identifierNamesGenerator: 'hexadecimal',
     identifierNamesGenerator: 'hexadecimal',
     log: false,
     log: false,
+    numbersToExpressions: false,
     renameGlobals: false,
     renameGlobals: false,
     rotateStringArray: true,
     rotateStringArray: true,
     selfDefending: true,
     selfDefending: true,
@@ -1045,6 +1063,7 @@ Performance will slightly slower than without obfuscation
     disableConsoleOutput: true,
     disableConsoleOutput: true,
     identifierNamesGenerator: 'hexadecimal',
     identifierNamesGenerator: 'hexadecimal',
     log: false,
     log: false,
+    numbersToExpressions: false,
     renameGlobals: false,
     renameGlobals: false,
     rotateStringArray: true,
     rotateStringArray: true,
     selfDefending: true,
     selfDefending: true,

File diff suppressed because it is too large
+ 0 - 0
dist/index.browser.js


File diff suppressed because it is too large
+ 0 - 12460
dist/index.cli.js


File diff suppressed because it is too large
+ 0 - 11756
dist/index.js


+ 1 - 1
package.json

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

+ 1 - 0
src/JavaScriptObfuscator.ts

@@ -75,6 +75,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         NodeTransformer.MemberExpressionTransformer,
         NodeTransformer.MemberExpressionTransformer,
         NodeTransformer.MetadataTransformer,
         NodeTransformer.MetadataTransformer,
         NodeTransformer.MethodDefinitionTransformer,
         NodeTransformer.MethodDefinitionTransformer,
+        NodeTransformer.NumberToNumericalExpressionTransformer,
         NodeTransformer.ObfuscatingGuardsTransformer,
         NodeTransformer.ObfuscatingGuardsTransformer,
         NodeTransformer.ObjectExpressionKeysTransformer,
         NodeTransformer.ObjectExpressionKeysTransformer,
         NodeTransformer.ObjectExpressionTransformer,
         NodeTransformer.ObjectExpressionTransformer,

+ 35 - 30
src/analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer.ts

@@ -7,15 +7,17 @@ import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 
 
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
+import { NumberUtils } from '../../utils/NumberUtils';
+
 /**
 /**
- * Rework of https://gist.github.com/da411d/0e59f79dcf4603cdabf0024a10eeb6fe
+ * Based on https://gist.github.com/da411d/0e59f79dcf4603cdabf0024a10eeb6fe
  */
  */
 @injectable()
 @injectable()
 export class NumberNumericalExpressionAnalyzer implements INumberNumericalExpressionAnalyzer {
 export class NumberNumericalExpressionAnalyzer implements INumberNumericalExpressionAnalyzer {
     /**
     /**
      * @type {number}
      * @type {number}
      */
      */
-    private static readonly additionalParts: number = 5;
+    private static readonly additionalParts: number = 3;
 
 
     /**
     /**
      * @type {IRandomGenerator}
      * @type {IRandomGenerator}
@@ -40,6 +42,10 @@ export class NumberNumericalExpressionAnalyzer implements INumberNumericalExpres
             throw new Error('Given value is NaN');
             throw new Error('Given value is NaN');
         }
         }
 
 
+        if (NumberUtils.isUnsafeNumber(number)) {
+            return [number];
+        }
+
         const additionParts: number[] = this.generateAdditionParts(number);
         const additionParts: number[] = this.generateAdditionParts(number);
 
 
         return additionParts.map((addition: number) => this.mixWithMultiplyParts(addition));
         return additionParts.map((addition: number) => this.mixWithMultiplyParts(addition));
@@ -52,8 +58,10 @@ export class NumberNumericalExpressionAnalyzer implements INumberNumericalExpres
     private generateAdditionParts (number: number): number[] {
     private generateAdditionParts (number: number): number[] {
         const additionParts = [];
         const additionParts = [];
 
 
-        const from: number = Math.min(-10000, -Math.abs(number * 2));
-        const to: number = Math.max(10000, Math.abs(number * 2));
+        const upperNumberLimit: number = Math.min(Math.abs(number * 2), Number.MAX_SAFE_INTEGER);
+
+        const from: number = Math.min(-10000, -upperNumberLimit);
+        const to: number = Math.max(10000, upperNumberLimit);
 
 
         let temporarySum = 0;
         let temporarySum = 0;
 
 
@@ -61,14 +69,27 @@ export class NumberNumericalExpressionAnalyzer implements INumberNumericalExpres
             if (i < NumberNumericalExpressionAnalyzer.additionalParts - 1) {
             if (i < NumberNumericalExpressionAnalyzer.additionalParts - 1) {
                 // trailing parts
                 // trailing parts
 
 
-                const addition: number = this.randomGenerator.getRandomInteger(from, to);
+                let addition: number = this.randomGenerator.getRandomInteger(from, to);
+                const isUnsafeCombination: boolean = NumberUtils.isUnsafeNumber(temporarySum + addition);
+
+                // we have to flip sign if total expression sum overflows over safe integer limits
+                if (isUnsafeCombination) {
+                    addition = -addition;
+                }
 
 
                 additionParts.push(addition);
                 additionParts.push(addition);
                 temporarySum += addition;
                 temporarySum += addition;
             } else {
             } else {
-                // last part
+                const combination: number = number - temporarySum;
+                const isUnsafeCombination: boolean = NumberUtils.isUnsafeNumber(combination);
 
 
-                additionParts.push(number - temporarySum);
+                // last part
+                if (isUnsafeCombination) {
+                    additionParts.push(0 - temporarySum);
+                    additionParts.push(number);
+                } else {
+                    additionParts.push(combination);
+                }
             }
             }
         }
         }
 
 
@@ -80,36 +101,20 @@ export class NumberNumericalExpressionAnalyzer implements INumberNumericalExpres
      * @returns {number | number[]}
      * @returns {number | number[]}
      */
      */
     private mixWithMultiplyParts (number: number): number | number[] {
     private mixWithMultiplyParts (number: number): number | number[] {
-        const dividers: number[] = this.getDividers(number);
-
         const shouldMixWithMultiplyParts: boolean = this.randomGenerator.getMathRandom() > 0.5;
         const shouldMixWithMultiplyParts: boolean = this.randomGenerator.getMathRandom() > 0.5;
 
 
-        if (!shouldMixWithMultiplyParts || !dividers.length) {
+        if (!shouldMixWithMultiplyParts) {
             return number;
             return number;
         }
         }
 
 
-        const divider = dividers[
-            this.randomGenerator.getRandomInteger(0, dividers.length - 1)
-        ];
-
-        return [divider, number / divider];
-    }
+        const factors: number[] = NumberUtils.getFactors(number);
 
 
-    /**
-     * @param {number} number
-     * @returns {number[]}
-     */
-    private getDividers (number: number): number[] {
-        const dividers: number[] = [];
-
-        number = Math.abs(number);
-
-        for (let i = 2; i < number; i++) {
-            if (number % i === 0){
-                dividers.push(i);
-            }
+        if (!factors.length) {
+            return number;
         }
         }
 
 
-        return dividers;
+        const factor: number = factors[this.randomGenerator.getRandomInteger(0, factors.length - 1)];
+
+        return [factor, number / factor];
     }
     }
 }
 }

+ 4 - 0
src/cli/JavaScriptObfuscatorCLI.ts

@@ -249,6 +249,10 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
                 '--log <boolean>', 'Enables logging of the information to the console',
                 '--log <boolean>', 'Enables logging of the information to the console',
                 BooleanSanitizer
                 BooleanSanitizer
             )
             )
+            .option(
+                '--numbers-to-expressions <boolean>', 'Enables numbers conversion to expressions',
+                BooleanSanitizer
+            )
             .option(
             .option(
                 '--reserved-names <list> (comma separated, without whitespaces)',
                 '--reserved-names <list> (comma separated, without whitespaces)',
                 'Disables obfuscation and generation of identifiers, which being matched by passed RegExp patterns (comma separated)',
                 'Disables obfuscation and generation of identifiers, which being matched by passed RegExp patterns (comma separated)',

+ 7 - 1
src/container/modules/node-transformers/ConvertingTransformersModule.ts

@@ -8,14 +8,15 @@ import { IObjectExpressionExtractor } from '../../../interfaces/node-transformer
 import { NodeTransformer } from '../../../enums/node-transformers/NodeTransformer';
 import { NodeTransformer } from '../../../enums/node-transformers/NodeTransformer';
 import { ObjectExpressionExtractor } from '../../../enums/node-transformers/converting-transformers/properties-extractors/ObjectExpressionExtractor';
 import { ObjectExpressionExtractor } from '../../../enums/node-transformers/converting-transformers/properties-extractors/ObjectExpressionExtractor';
 
 
+import { BasePropertiesExtractor } from '../../../node-transformers/converting-transformers/object-expression-extractors/BasePropertiesExtractor';
 import { ObjectExpressionToVariableDeclarationExtractor } from '../../../node-transformers/converting-transformers/object-expression-extractors/ObjectExpressionToVariableDeclarationExtractor';
 import { ObjectExpressionToVariableDeclarationExtractor } from '../../../node-transformers/converting-transformers/object-expression-extractors/ObjectExpressionToVariableDeclarationExtractor';
 import { MemberExpressionTransformer } from '../../../node-transformers/converting-transformers/MemberExpressionTransformer';
 import { MemberExpressionTransformer } from '../../../node-transformers/converting-transformers/MemberExpressionTransformer';
 import { MethodDefinitionTransformer } from '../../../node-transformers/converting-transformers/MethodDefinitionTransformer';
 import { MethodDefinitionTransformer } from '../../../node-transformers/converting-transformers/MethodDefinitionTransformer';
+import { NumberToNumericalExpressionTransformer } from '../../../node-transformers/converting-transformers/NumberToNumericalExpressionTransformer';
 import { ObjectExpressionKeysTransformer } from '../../../node-transformers/converting-transformers/ObjectExpressionKeysTransformer';
 import { ObjectExpressionKeysTransformer } from '../../../node-transformers/converting-transformers/ObjectExpressionKeysTransformer';
 import { ObjectExpressionTransformer } from '../../../node-transformers/converting-transformers/ObjectExpressionTransformer';
 import { ObjectExpressionTransformer } from '../../../node-transformers/converting-transformers/ObjectExpressionTransformer';
 import { SplitStringTransformer } from '../../../node-transformers/converting-transformers/SplitStringTransformer';
 import { SplitStringTransformer } from '../../../node-transformers/converting-transformers/SplitStringTransformer';
 import { TemplateLiteralTransformer } from '../../../node-transformers/converting-transformers/TemplateLiteralTransformer';
 import { TemplateLiteralTransformer } from '../../../node-transformers/converting-transformers/TemplateLiteralTransformer';
-import { BasePropertiesExtractor } from '../../../node-transformers/converting-transformers/object-expression-extractors/BasePropertiesExtractor';
 
 
 export const convertingTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
 export const convertingTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
     // converting transformers
     // converting transformers
@@ -27,6 +28,11 @@ export const convertingTransformersModule: interfaces.ContainerModule = new Cont
         .to(MethodDefinitionTransformer)
         .to(MethodDefinitionTransformer)
         .whenTargetNamed(NodeTransformer.MethodDefinitionTransformer);
         .whenTargetNamed(NodeTransformer.MethodDefinitionTransformer);
 
 
+
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(NumberToNumericalExpressionTransformer)
+        .whenTargetNamed(NodeTransformer.NumberToNumericalExpressionTransformer);
+
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
         .to(ObjectExpressionKeysTransformer)
         .to(ObjectExpressionKeysTransformer)
         .whenTargetNamed(NodeTransformer.ObjectExpressionKeysTransformer);
         .whenTargetNamed(NodeTransformer.ObjectExpressionKeysTransformer);

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

@@ -11,6 +11,7 @@ export enum NodeTransformer {
     MemberExpressionTransformer = 'MemberExpressionTransformer',
     MemberExpressionTransformer = 'MemberExpressionTransformer',
     MetadataTransformer = 'MetadataTransformer',
     MetadataTransformer = 'MetadataTransformer',
     MethodDefinitionTransformer = 'MethodDefinitionTransformer',
     MethodDefinitionTransformer = 'MethodDefinitionTransformer',
+    NumberToNumericalExpressionTransformer = 'NumberToNumericalExpressionTransformer',
     ObfuscatingGuardsTransformer = 'ObfuscatingGuardsTransformer',
     ObfuscatingGuardsTransformer = 'ObfuscatingGuardsTransformer',
     ObjectExpressionKeysTransformer = 'ObjectExpressionKeysTransformer',
     ObjectExpressionKeysTransformer = 'ObjectExpressionKeysTransformer',
     ObjectExpressionTransformer = 'ObjectExpressionTransformer',
     ObjectExpressionTransformer = 'ObjectExpressionTransformer',

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

@@ -21,6 +21,7 @@ export interface IOptions {
     readonly identifiersPrefix: string;
     readonly identifiersPrefix: string;
     readonly inputFileName: string;
     readonly inputFileName: string;
     readonly log: boolean;
     readonly log: boolean;
+    readonly numbersToExpressions: boolean;
     readonly renameGlobals: boolean;
     readonly renameGlobals: boolean;
     readonly renameProperties: boolean;
     readonly renameProperties: boolean;
     readonly reservedNames: string[];
     readonly reservedNames: string[];

+ 175 - 0
src/node-transformers/converting-transformers/NumberToNumericalExpressionTransformer.ts

@@ -0,0 +1,175 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+
+import { TNumberNumericalExpressionData } from '../../types/analyzers/number-numerical-expression-analyzer/TNumberNumericalExpressionData';
+
+import { INumberNumericalExpressionAnalyzer } from '../../interfaces/analyzers/number-numerical-expression-analyzer/INumberNumericalExpressionAnalyzer';
+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 { NodeFactory } from '../../node/NodeFactory';
+import { NumberUtils } from '../../utils/NumberUtils';
+
+/**
+ * replaces:
+ *     var number = 123;
+ *
+ * on:
+ *     var number = 50 + (100 * 2) - 127;
+ */
+@injectable()
+export class NumberToNumericalExpressionTransformer extends AbstractNodeTransformer {
+    /**
+     * @type {INumberNumericalExpressionAnalyzer}
+     */
+    private readonly numberNumericalExpressionAnalyzer: INumberNumericalExpressionAnalyzer;
+
+    /**
+     * @param {INumberNumericalExpressionAnalyzer} numberNumericalExpressionAnalyzer
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.INumberNumericalExpressionAnalyzer)
+            numberNumericalExpressionAnalyzer: INumberNumericalExpressionAnalyzer,
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(randomGenerator, options);
+
+        this.numberNumericalExpressionAnalyzer = numberNumericalExpressionAnalyzer;
+    }
+
+    /**
+     * @param {NodeTransformationStage} nodeTransformationStage
+     * @returns {IVisitor | null}
+     */
+    public getVisitor (nodeTransformationStage: NodeTransformationStage): IVisitor | null {
+        if (!this.options.numbersToExpressions) {
+            return null;
+        }
+
+        switch (nodeTransformationStage) {
+            case NodeTransformationStage.Converting:
+                return {
+                    leave: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
+                        if (parentNode && NodeGuards.isLiteralNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * @param {Literal} literalNode
+     * @param {NodeGuards} parentNode
+     * @returns {NodeGuards}
+     */
+    public transformNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): ESTree.Node {
+        if (typeof literalNode.value !== 'number') {
+            return literalNode;
+        }
+
+        if (NodeGuards.isPropertyNode(parentNode) && !parentNode.computed) {
+            return literalNode;
+        }
+
+        const numberNumericalExpressionData: TNumberNumericalExpressionData = this.numberNumericalExpressionAnalyzer.analyze(literalNode.value);
+
+        return this.convertNumericalExpressionDataToNode(numberNumericalExpressionData);
+    }
+
+    /**
+     * @param {TNumberNumericalExpressionData} numberNumericalExpressionData
+     * @param {ESTree.BinaryOperator} operator
+     * @returns {ESTree.BinaryExpression | ESTree.Literal | ESTree.UnaryExpression}
+     */
+    private convertNumericalExpressionDataToNode (
+        numberNumericalExpressionData: TNumberNumericalExpressionData,
+        operator: ESTree.BinaryOperator = '+'
+    ): ESTree.BinaryExpression | ESTree.Literal | ESTree.UnaryExpression {
+        const numberNumericalExpressionDataLength: number = numberNumericalExpressionData.length;
+
+        const leftParts: TNumberNumericalExpressionData = numberNumericalExpressionDataLength > 1
+            ? numberNumericalExpressionData.slice(0, numberNumericalExpressionDataLength - 1)
+            : [numberNumericalExpressionData[0]];
+        const rightParts: TNumberNumericalExpressionData = numberNumericalExpressionDataLength > 1
+            ? numberNumericalExpressionData.slice(-1)
+            : [];
+
+        // trailing iterations
+        if (rightParts.length) {
+            return this.convertPartsToBinaryExpression(operator, leftParts, rightParts);
+        }
+
+        const firstLeftPartOrNumber: number | number[] | null = leftParts[0] ?? null;
+
+        // last iteration when only single left part is left
+        return Array.isArray(firstLeftPartOrNumber)
+            ? this.convertNumericalExpressionDataToNode(firstLeftPartOrNumber, '*')
+            : this.convertPartOrNumberToLiteralNode(firstLeftPartOrNumber);
+    }
+
+    /**
+     * @param {ESTree.BinaryOperator} operator
+     * @param {TNumberNumericalExpressionData} leftParts
+     * @param {TNumberNumericalExpressionData} rightParts
+     * @returns {ESTree.BinaryExpression}
+     */
+    private convertPartsToBinaryExpression (
+        operator: ESTree.BinaryOperator,
+        leftParts: TNumberNumericalExpressionData,
+        rightParts: TNumberNumericalExpressionData
+    ): ESTree.BinaryExpression {
+        const rightPartOrNumber: number | number[] = rightParts[0];
+
+        if (Array.isArray(rightPartOrNumber)) {
+            // right part is array with multiply numbers
+            return NodeFactory.binaryExpressionNode(
+                operator,
+                this.convertNumericalExpressionDataToNode(leftParts),
+                this.convertNumericalExpressionDataToNode(rightPartOrNumber, '*')
+            );
+        } else {
+            // right part is number
+            return NodeFactory.binaryExpressionNode(
+                operator,
+                this.convertNumericalExpressionDataToNode(leftParts),
+                this.convertPartOrNumberToLiteralNode(rightPartOrNumber)
+            );
+        }
+    }
+
+    /**
+     * @param {number | number[]} partOrNumber
+     * @returns {ESTree.Literal}
+     */
+    private convertPartOrNumberToLiteralNode (
+        partOrNumber: number | number[]
+    ): ESTree.Literal | ESTree.UnaryExpression {
+        const number: number = Array.isArray(partOrNumber)
+            ? partOrNumber[0]
+            : partOrNumber;
+        const isPositiveNumber: boolean = NumberUtils.isPositive(number);
+
+        const literalNode: ESTree.Literal = NodeFactory.literalNode(Math.abs(number));
+
+        return isPositiveNumber
+            ? literalNode
+            : NodeFactory.unaryExpressionNode(
+                '-',
+                literalNode
+            );
+    }
+}

+ 6 - 0
src/options/Options.ts

@@ -151,6 +151,12 @@ export class Options implements IOptions {
     @IsBoolean()
     @IsBoolean()
     public readonly log!: boolean;
     public readonly log!: boolean;
 
 
+    /**
+     * @type {boolean}
+     */
+    @IsBoolean()
+    public readonly numbersToExpressions!: boolean;
+
     /**
     /**
      * @type {boolean}
      * @type {boolean}
      */
      */

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

@@ -21,6 +21,7 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     identifiersDictionary: [],
     identifiersDictionary: [],
     inputFileName: '',
     inputFileName: '',
     log: false,
     log: false,
+    numbersToExpressions: false,
     renameGlobals: false,
     renameGlobals: false,
     renameProperties: false,
     renameProperties: false,
     reservedNames: [],
     reservedNames: [],

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

@@ -20,6 +20,7 @@ export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({
     identifiersDictionary: [],
     identifiersDictionary: [],
     inputFileName: '',
     inputFileName: '',
     log: false,
     log: false,
+    numbersToExpressions: false,
     renameGlobals: false,
     renameGlobals: false,
     renameProperties: false,
     renameProperties: false,
     reservedNames: [],
     reservedNames: [],

+ 81 - 0
src/utils/NumberUtils.ts

@@ -24,4 +24,85 @@ export class NumberUtils {
             ? number % 1 === 0
             ? number % 1 === 0
             : true;
             : true;
     }
     }
+
+    /**
+     * @param {number} number
+     * @returns {boolean}
+     */
+    public static isPositive (number: number): boolean {
+        if (isNaN(number)) {
+            throw new Error('Given value is NaN');
+        }
+
+        if (number > 0) {
+            return true;
+        }
+
+        if (number < 0) {
+            return false;
+        }
+
+        if (1 / number === Number.POSITIVE_INFINITY) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * @param {number} number
+     * @returns {boolean}
+     */
+    public static isUnsafeNumber (number: number): boolean {
+        if (isNaN(number)) {
+            throw new Error('Given value is NaN');
+        }
+
+        return number < Number.MIN_SAFE_INTEGER || number > Number.MAX_SAFE_INTEGER;
+    }
+
+    /**
+     * Returns all factors of a number
+     * Based on https://stackoverflow.com/a/43204663
+     *
+     * @param {number} number
+     * @returns {number[]}
+     */
+    public static getFactors (number: number): number[] {
+        if (number === 0) {
+            throw new Error('Invalid number. Allowed only non-zero number');
+        }
+
+        number = Math.abs(number);
+
+        // special case for 1
+        if (number === 1) {
+            return [-number, number];
+        }
+
+        const factors: number[] = [];
+
+        const isEven: boolean = number % 2 === 0;
+        const incrementValue: number = isEven ? 1 : 2;
+
+        for (
+            let currentFactor = 1;
+            Math.pow(currentFactor, 2) <= number;
+            currentFactor += incrementValue
+        ) {
+            if (number % currentFactor !== 0) {
+                continue;
+            }
+
+            factors.push(...[-currentFactor, currentFactor]);
+
+            const compliment: number = number / currentFactor;
+
+            if (compliment !== currentFactor) {
+                factors.push(...[-compliment, compliment]);
+            }
+        }
+
+        return factors.sort((a: number, b: number) => a - b);
+    }
 }
 }

+ 3 - 12
test/dev/dev.ts

@@ -1,6 +1,5 @@
 'use strict';
 'use strict';
 
 
-import { IdentifierNamesGenerator } from '../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
 import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNodes';
 import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNodes';
 
 
 (function () {
 (function () {
@@ -8,21 +7,13 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
 
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
         `
-            const example1 = 0;
-            const example2 = 1;
-            const example3 = 100;
-            const example4 = 125793;
-            const example5 = -15232103;
+            const foo = 100;
             
             
-            console.log(example1);
-            console.log(example2);
-            console.log(example3);
-            console.log(example4);
-            console.log(example5);
+            console.log(foo);
         `,
         `,
         {
         {
             ...NO_ADDITIONAL_NODES_PRESET,
             ...NO_ADDITIONAL_NODES_PRESET,
-            identifierNamesGenerator: IdentifierNamesGenerator.MangledShuffledIdentifierNamesGenerator
+            numbersToExpressions: true
         }
         }
     ).getObfuscatedCode();
     ).getObfuscatedCode();
 
 

+ 167 - 0
test/functional-tests/node-transformers/converting-transformers/numbers-to-numerical-expressions-transformer/NumbersToNumericalExpressionsTransformer.spec.ts

@@ -0,0 +1,167 @@
+import { assert } from 'chai';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+
+import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
+import { readFileAsString } from '../../../../helpers/readFileAsString';
+
+describe('NumbersToNumericalExpressionsTransformer', function () {
+    this.timeout(60000);
+
+    describe('Variant #1: base', () => {
+        const initialNumber: number = -50;
+        const lastNumber: number = 50;
+
+        let areValidExpressions: boolean = true;
+
+        before(() => {
+            for (let i = initialNumber; i < lastNumber; i++) {
+                const number: number = i;
+                const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
+                    `${number};`,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        numbersToExpressions: true
+                    }
+                ).getObfuscatedCode();
+
+                const result: number = eval(obfuscatedCode);
+
+                if (result !== number) {
+                    areValidExpressions = false;
+                    break;
+                }
+            }
+        });
+
+        it('should correctly transform numbers to expressions', () => {
+            assert.isTrue(areValidExpressions);
+        });
+    });
+
+    describe('Variant #2: safe integers', () => {
+        describe('Variant #1: max safe integer', () => {
+            const number: number = Number.MAX_SAFE_INTEGER;
+            const samplesCount: number = 15;
+
+            let areValidExpressions: boolean = true;
+
+            before(() => {
+                for (let i = 0; i < samplesCount; i++) {
+                    const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
+                        `${number};`,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            numbersToExpressions: true
+                        }
+                    ).getObfuscatedCode();
+
+                    const result: number = eval(obfuscatedCode);
+
+                    if (result !== number) {
+                        areValidExpressions = false;
+                        break;
+                    }
+                }
+            });
+
+            it('should correctly transform numbers to expressions', () => {
+                assert.isTrue(areValidExpressions);
+            });
+        });
+
+        describe('Variant #2: max unsafe integer', () => {
+            const unsafeIntegerRegExp: RegExp = /0x20000000000000;/;
+            const number: number = Number.MAX_SAFE_INTEGER + 1;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    `${number};`,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        numbersToExpressions: true
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should not transform unsafe integer to expressions', () => {
+                assert.match(obfuscatedCode, unsafeIntegerRegExp);
+            });
+        });
+
+        describe('Variant #3: min safe integer', () => {
+            const number: number = Number.MIN_SAFE_INTEGER;
+            const samplesCount: number = 15;
+
+            let areValidExpressions: boolean = true;
+
+            before(() => {
+                for (let i = 0; i < samplesCount; i++) {
+                    const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
+                        `${number};`,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            numbersToExpressions: true
+                        }
+                    ).getObfuscatedCode();
+
+                    const result: number = eval(obfuscatedCode);
+
+                    if (result !== number) {
+                        areValidExpressions = false;
+                        break;
+                    }
+                }
+            });
+
+            it('should correctly transform numbers to expressions', () => {
+                assert.isTrue(areValidExpressions);
+            });
+        });
+
+        describe('Variant #4: min unsafe integer', () => {
+            const unsafeIntegerRegExp: RegExp = /-0x20000000000000;/;
+            const number: number = Number.MIN_SAFE_INTEGER - 1;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    `${number};`,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        numbersToExpressions: true
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should not transform unsafe integer to expressions', () => {
+                assert.match(obfuscatedCode, unsafeIntegerRegExp);
+            });
+        });
+    });
+
+    describe('Variant #3: parent node is non-computed object property', () => {
+        const regExp: RegExp = /const foo *= *{1: *'bar'};/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/non-computed-object-key.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    numbersToExpressions: true
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('should not replace non-computed object property literal with expression', () => {
+            assert.match(obfuscatedCode,  regExp);
+        });
+    });
+});

+ 3 - 0
test/functional-tests/node-transformers/converting-transformers/numbers-to-numerical-expressions-transformer/fixtures/non-computed-object-key.js

@@ -0,0 +1,3 @@
+const foo = {
+    1: 'bar'
+};

+ 1 - 0
test/index.spec.ts

@@ -88,6 +88,7 @@ import './functional-tests/node-transformers/control-flow-transformers/control-f
 import './functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/string-litertal-control-flow-replacer/StringLiteralControlFlowReplacer.spec';
 import './functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/string-litertal-control-flow-replacer/StringLiteralControlFlowReplacer.spec';
 import './functional-tests/node-transformers/converting-transformers/member-expression-transformer/MemberExpressionTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/member-expression-transformer/MemberExpressionTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/method-definition-transformer/MethodDefinitionTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/method-definition-transformer/MethodDefinitionTransformer.spec';
+import './functional-tests/node-transformers/converting-transformers/numbers-to-numerical-expressions-transformer/NumbersToNumericalExpressionsTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/ObjectExpressionKeysTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/ObjectExpressionKeysTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/object-expression-transformer/ObjectExpressionTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/object-expression-transformer/ObjectExpressionTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/split-string-transformer/SplitStringTransformer.spec';
 import './functional-tests/node-transformers/converting-transformers/split-string-transformer/SplitStringTransformer.spec';

+ 108 - 27
test/unit-tests/analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer.spec.ts

@@ -21,9 +21,11 @@ const numberNumericalExpressionDataToString = (data: TNumberNumericalExpressionD
         .join('+')
         .join('+')
         .replace(/\+-/g, '-');
         .replace(/\+-/g, '-');
 
 
-describe('NumberNumericalExpressionAnalyzer', () => {
+describe('NumberNumericalExpressionAnalyzer', function() {
     let numberNumericalExpressionAnalyzer: INumberNumericalExpressionAnalyzer;
     let numberNumericalExpressionAnalyzer: INumberNumericalExpressionAnalyzer;
 
 
+    this.timeout(10000);
+
     before(() => {
     before(() => {
         const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
         const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
 
 
@@ -35,52 +37,131 @@ describe('NumberNumericalExpressionAnalyzer', () => {
     describe('analyze', () => {
     describe('analyze', () => {
         let evaluatedResult: number;
         let evaluatedResult: number;
 
 
-        describe('Variant #1: positive number', () => {
-            const number: number = 1234;
+        describe('Positive numbers', () => {
+            describe('Variant #1: positive number', () => {
+                const number: number = 1234;
 
 
-            before(() => {
-                const numberNumericalExpressionData: TNumberNumericalExpressionData =
-                    numberNumericalExpressionAnalyzer.analyze(number);
+                before(() => {
+                    const numberNumericalExpressionData: TNumberNumericalExpressionData =
+                        numberNumericalExpressionAnalyzer.analyze(number);
 
 
-                evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                    evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                });
+
+                it('should return correct number numerical expression data', () => {
+                    assert.equal(number, evaluatedResult);
+                });
             });
             });
 
 
-            it('should return correct number numerical expression data', () => {
-                assert.equal(number, evaluatedResult);
+            describe('Variant #2: positive zero number', () => {
+                const number: number = 0;
+
+                before(() => {
+                    const numberNumericalExpressionData: TNumberNumericalExpressionData =
+                        numberNumericalExpressionAnalyzer.analyze(number);
+
+                    evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                });
+
+                it('should return correct number numerical expression data', () => {
+                    assert.equal(number, evaluatedResult);
+                });
             });
             });
-        });
 
 
-        describe('Variant #2: negative number', () => {
-            const number: number = -1234;
+            describe('Variant #3: positive big number', () => {
+                const number: number = Number.MAX_SAFE_INTEGER;
 
 
-            before(() => {
-                const numberNumericalExpressionData: TNumberNumericalExpressionData =
-                    numberNumericalExpressionAnalyzer.analyze(number);
+                before(() => {
+                    const numberNumericalExpressionData: TNumberNumericalExpressionData =
+                        numberNumericalExpressionAnalyzer.analyze(number);
+
+                    evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                });
 
 
-                evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                it('should return correct number numerical expression data', () => {
+                    assert.equal(number, evaluatedResult);
+                });
             });
             });
 
 
-            it('should return correct number numerical expression data', () => {
-                assert.equal(number, evaluatedResult);
+            describe('Variant #4: positive unsafe big number', () => {
+                const number: number = Number.MAX_SAFE_INTEGER + 1;
+
+                before(() => {
+                    const numberNumericalExpressionData: TNumberNumericalExpressionData =
+                        numberNumericalExpressionAnalyzer.analyze(number);
+
+                    evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                });
+
+                it('should return correct number numerical expression data', () => {
+                    assert.equal(number, evaluatedResult);
+                });
             });
             });
         });
         });
 
 
-        describe('Variant #3: zero number', () => {
-            const number: number = 0;
+        describe('Negative numbers', () => {
+            describe('Variant #1: negative number', () => {
+                const number: number = -1234;
 
 
-            before(() => {
-                const numberNumericalExpressionData: TNumberNumericalExpressionData =
-                    numberNumericalExpressionAnalyzer.analyze(number);
+                before(() => {
+                    const numberNumericalExpressionData: TNumberNumericalExpressionData =
+                        numberNumericalExpressionAnalyzer.analyze(number);
+
+                    evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                });
 
 
-                evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                it('should return correct number numerical expression data', () => {
+                    assert.equal(number, evaluatedResult);
+                });
             });
             });
 
 
-            it('should return correct number numerical expression data', () => {
-                assert.equal(number, evaluatedResult);
+            describe('Variant #2: negative zero number', () => {
+                const number: number = -0;
+
+                before(() => {
+                    const numberNumericalExpressionData: TNumberNumericalExpressionData =
+                        numberNumericalExpressionAnalyzer.analyze(number);
+
+                    evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                });
+
+                it('should return correct number numerical expression data', () => {
+                    assert.equal(number, evaluatedResult);
+                });
+            });
+
+            describe('Variant #3: negative big number', () => {
+                const number: number = Number.MIN_SAFE_INTEGER;
+
+                before(() => {
+                    const numberNumericalExpressionData: TNumberNumericalExpressionData =
+                        numberNumericalExpressionAnalyzer.analyze(number);
+
+                    evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                });
+
+                it('should return correct number numerical expression data', () => {
+                    assert.equal(number, evaluatedResult);
+                });
+            });
+
+            describe('Variant #4: negative unsafe number', () => {
+                const number: number = Number.MIN_SAFE_INTEGER - 1;
+
+                before(() => {
+                    const numberNumericalExpressionData: TNumberNumericalExpressionData =
+                        numberNumericalExpressionAnalyzer.analyze(number);
+
+                    evaluatedResult = eval(numberNumericalExpressionDataToString(numberNumericalExpressionData));
+                });
+
+                it('should return correct number numerical expression data', () => {
+                    assert.equal(number, evaluatedResult);
+                });
             });
             });
         });
         });
 
 
-        describe('Variant #4: NaN', () => {
+        describe('NaN', () => {
             const number: number = NaN;
             const number: number = NaN;
 
 
             let testFunc: () => void;
             let testFunc: () => void;

+ 414 - 0
test/unit-tests/utils/NumberUtils.spec.ts

@@ -128,4 +128,418 @@ describe('NumberUtils', () => {
             });
             });
         });
         });
     });
     });
+
+    describe('isPositive', () => {
+        describe('Variant #1: positive integer', () => {
+            const number: number = 2;
+            const expectedResult: boolean = true;
+
+            let result: boolean;
+
+            before(() => {
+                result = NumberUtils.isPositive(number);
+            });
+
+            it('should return true', () => {
+                assert.equal(result, expectedResult);
+            });
+        });
+
+        describe('Variant #2: positive zero', () => {
+            const number: number = 0;
+            const expectedResult: boolean = true;
+
+            let result: boolean;
+
+            before(() => {
+                result = NumberUtils.isPositive(number);
+            });
+
+            it('should return true', () => {
+                assert.equal(result, expectedResult);
+            });
+        });
+
+        describe('Variant #3: negative integer', () => {
+            const number: number = -2;
+            const expectedResult: boolean = false;
+
+            let result: boolean;
+
+            before(() => {
+                result = NumberUtils.isPositive(number);
+            });
+
+            it('should return false', () => {
+                assert.equal(result, expectedResult);
+            });
+        });
+
+        describe('Variant #4: negative zero', () => {
+            const number: number = -0;
+            const expectedResult: boolean = false;
+
+            let result: boolean;
+
+            before(() => {
+                result = NumberUtils.isPositive(number);
+            });
+
+            it('should return false', () => {
+                assert.equal(result, expectedResult);
+            });
+        });
+
+        describe('Variant #5: NaN', () => {
+            const number: number = NaN;
+
+            let testFunc: () => void;
+
+            before(() => {
+                testFunc = () => NumberUtils.isPositive(number);
+            });
+
+            it('should throw an error', () => {
+                assert.throw(testFunc, 'Given value is NaN');
+            });
+        });
+    });
+
+    describe('isUnsafeNumber', () => {
+        describe('Positive number', () => {
+            describe('Variant #1: positive small safe integer', () => {
+                const number: number = 100;
+                const expectedResult: boolean = false
+
+                let result: boolean;
+
+                before(() => {
+                    result = NumberUtils.isUnsafeNumber(number);
+                });
+
+                it('should return false', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #2: positive big safe integer', () => {
+                const number: number = Number.MAX_SAFE_INTEGER;
+                const expectedResult: boolean = false
+
+                let result: boolean;
+
+                before(() => {
+                    result = NumberUtils.isUnsafeNumber(number);
+                });
+
+                it('should return false', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #3: positive unsafe integer', () => {
+                const number: number = Number.MAX_SAFE_INTEGER + 1;
+                const expectedResult: boolean = true
+
+                let result: boolean;
+
+                before(() => {
+                    result = NumberUtils.isUnsafeNumber(number);
+                });
+
+                it('should return true', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+        });
+
+        describe('Negative number', () => {
+            describe('Variant #1: negative small safe integer', () => {
+                const number: number = -100;
+                const expectedResult: boolean = false
+
+                let result: boolean;
+
+                before(() => {
+                    result = NumberUtils.isUnsafeNumber(number);
+                });
+
+                it('should return false', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #2: negative big safe integer', () => {
+                const number: number = Number.MIN_SAFE_INTEGER;
+                const expectedResult: boolean = false
+
+                let result: boolean;
+
+                before(() => {
+                    result = NumberUtils.isUnsafeNumber(number);
+                });
+
+                it('should return false', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #3: negative unsafe integer', () => {
+                const number: number = Number.MIN_SAFE_INTEGER - 1;
+                const expectedResult: boolean = true
+
+                let result: boolean;
+
+                before(() => {
+                    result = NumberUtils.isUnsafeNumber(number);
+                });
+
+                it('should return true', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+        });
+
+        describe('NaN', () => {
+            const number: number = NaN;
+
+            let testFunc: () => void;
+
+            before(() => {
+                testFunc = () => NumberUtils.isUnsafeNumber(number);
+            });
+
+            it('should throw an error', () => {
+                assert.throw(testFunc, 'Given value is NaN');
+            });
+        });
+    });
+
+    describe('getFactors', () => {
+        describe('Positive numbers', () => {
+            describe('Variant #1: positive number `1`', () => {
+                const number: number = 1;
+                const expectedFactors: number[] = [
+                    -1,
+                    1
+                ];
+
+                let factors: number[];
+
+                before(() => {
+                    factors = NumberUtils.getFactors(number);
+                });
+
+                it('should return factors of a number', () => {
+                    assert.deepEqual(factors, expectedFactors);
+                });
+            });
+
+            describe('Variant #2: positive number `2`', () => {
+                const number: number = 2;
+                const expectedFactors: number[] = [
+                    -2,
+                    -1,
+                    1,
+                    2
+                ];
+
+                let factors: number[];
+
+                before(() => {
+                    factors = NumberUtils.getFactors(number);
+                });
+
+                it('should return factors of a number', () => {
+                    assert.deepEqual(factors, expectedFactors);
+                });
+            });
+
+            describe('Variant #3: positive number `100`', () => {
+                const number: number = 100;
+                const expectedFactors: number[] = [
+                    -100,
+                    -50,
+                    -25,
+                    -20,
+                    -10,
+                    -5,
+                    -4,
+                    -2,
+                    -1,
+                    1,
+                    2,
+                    4,
+                    5,
+                    10,
+                    20,
+                    25,
+                    50,
+                    100
+                ];
+
+                let factors: number[];
+
+                before(() => {
+                    factors = NumberUtils.getFactors(number);
+                });
+
+                it('should return factors of a number', () => {
+                    assert.deepEqual(factors, expectedFactors);
+                });
+            });
+
+            describe('Variant #4: positive number `9007199254740991`', () => {
+                const number: number = 9007199254740991;
+                const expectedFactors: number[] = [
+                    Number.MIN_SAFE_INTEGER,
+                    -1416003655831,
+                    -129728784761,
+                    -441650591,
+                    -20394401,
+                    -69431,
+                    -6361,
+                    -1,
+                    1,
+                    6361,
+                    69431,
+                    20394401,
+                    441650591,
+                    129728784761,
+                    1416003655831,
+                    Number.MAX_SAFE_INTEGER
+                ];
+
+                let factors: number[];
+
+                before(() => {
+                    factors = NumberUtils.getFactors(number);
+                });
+
+                it('should return factors of a number', () => {
+                    assert.deepEqual(factors, expectedFactors);
+                });
+            });
+        })
+
+        describe('Negative numbers', () => {
+            describe('Variant #1: negative number `-1`', () => {
+                const number: number = -1;
+                const expectedFactors: number[] = [
+                    -1,
+                    1
+                ];
+
+                let factors: number[];
+
+                before(() => {
+                    factors = NumberUtils.getFactors(number);
+                });
+
+                it('should return factors of a number', () => {
+                    assert.deepEqual(factors, expectedFactors);
+                });
+            });
+
+            describe('Variant #2: negative number `-2`', () => {
+                const number: number = -2;
+                const expectedFactors: number[] = [
+                    -2,
+                    -1,
+                    1,
+                    2
+                ];
+
+                let factors: number[];
+
+                before(() => {
+                    factors = NumberUtils.getFactors(number);
+                });
+
+                it('should return factors of a number', () => {
+                    assert.deepEqual(factors, expectedFactors);
+                });
+            });
+
+            describe('Variant #3: negative number `-100`', () => {
+                const number: number = -100;
+                const expectedFactors: number[] = [
+                    -100,
+                    -50,
+                    -25,
+                    -20,
+                    -10,
+                    -5,
+                    -4,
+                    -2,
+                    -1,
+                    1,
+                    2,
+                    4,
+                    5,
+                    10,
+                    20,
+                    25,
+                    50,
+                    100
+                ];
+
+                let factors: number[];
+
+                before(() => {
+                    factors = NumberUtils.getFactors(number);
+                });
+
+                it('should return factors of a number', () => {
+                    assert.deepEqual(factors, expectedFactors);
+                });
+            });
+
+            describe('Variant #4: negative number `-9007199254740991`', () => {
+                const number: number = -9007199254740991;
+                const expectedFactors: number[] = [
+                    Number.MIN_SAFE_INTEGER,
+                    -1416003655831,
+                    -129728784761,
+                    -441650591,
+                    -20394401,
+                    -69431,
+                    -6361,
+                    -1,
+                    1,
+                    6361,
+                    69431,
+                    20394401,
+                    441650591,
+                    129728784761,
+                    1416003655831,
+                    Number.MAX_SAFE_INTEGER
+                ];
+
+                let factors: number[];
+
+                before(() => {
+                    factors = NumberUtils.getFactors(number);
+                });
+
+                it('should return factors of a number', () => {
+                    assert.deepEqual(factors, expectedFactors);
+                });
+            });
+        })
+
+        describe('zero number', () => {
+            const number: number = 0;
+
+            let testFunc: () => void;
+
+            before(() => {
+                testFunc = () => NumberUtils.getFactors(number);
+            });
+
+            it('should throw an error', () => {
+                assert.throw(testFunc, Error);
+            });
+        })
+    });
 });
 });

Some files were not shown because too many files changed in this diff