瀏覽代碼

**New option**: `sourceMapSourcesMode` allows to control `sources` and `sourcesContent` fields of the source map
Fixed some cases with wrong source map file name generation when `sourceMapFileName` option is set

sanex 3 年之前
父節點
當前提交
b3fc8bc595

+ 5 - 0
CHANGELOG.md

@@ -1,5 +1,10 @@
 Change Log
 
+v2.17.0
+---
+* **New option**: `sourceMapSourcesMode` allows to control `sources` and `sourcesContent` fields of the source map
+* Fixed some cases with wrong source map file name generation when `sourceMapFileName` option is set
+
 v2.16.0
 ---
 * `stringArrayWrappersType: 'function'` now generates different indexes between each wrapper inside the same lexical scope

+ 9 - 0
README.md

@@ -377,6 +377,7 @@ Following options are available for the JS Obfuscator:
     sourceMapBaseUrl: '',
     sourceMapFileName: '',
     sourceMapMode: 'separate',
+    sourceMapSourcesMode: 'sources-content',
     splitStrings: false,
     splitStringsChunkLength: 10,
     stringArray: true,
@@ -438,6 +439,7 @@ Following options are available for the JS Obfuscator:
     --source-map-base-url <string>
     --source-map-file-name <string>
     --source-map-mode <string> [inline, separate]
+    --source-map-sources-mode <string> [sources, sources-content]
     --split-strings <boolean>
     --split-strings-chunk-length <number>
     --string-array <boolean>
@@ -1071,6 +1073,13 @@ Specifies source map generation mode:
 * `inline` - add source map at the end of each .js files;
 * `separate` - generates corresponding '.map' file with source map. In case you run obfuscator through CLI - adds link to source map file to the end of file with obfuscated code `//# sourceMappingUrl=file.js.map`.
 
+### `sourceMapSourcesMode`
+Type: `string` Default: `sources-content`
+
+Allows to control `sources` and `sourcesContent` fields of the source map:
+* `sources-content` - adds dummy `sources` field, adds `sourcesContent` field with the original source code;
+* `sources` - adds `sources` field with a valid source description, does not add `sourcesContent` field.
+
 ### `splitStrings`
 Type: `boolean` Default: `false`
 

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


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


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


+ 25 - 24
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "2.16.0",
+  "version": "2.17.0",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -24,12 +24,12 @@
     "@javascript-obfuscator/escodegen": "2.2.0",
     "@javascript-obfuscator/estraverse": "5.3.0",
     "@nuxtjs/opencollective": "0.3.2",
-    "acorn": "8.4.0",
+    "acorn": "8.4.1",
     "assert": "2.0.0",
     "chalk": "4.1.1",
     "chance": "1.1.7",
     "class-validator": "0.13.1",
-    "commander": "7.2.0",
+    "commander": "8.0.0",
     "eslint-scope": "5.1.1",
     "fast-deep-equal": "3.1.3",
     "inversify": "5.1.1",
@@ -46,46 +46,47 @@
   },
   "devDependencies": {
     "@istanbuljs/nyc-config-typescript": "1.0.1",
-    "@types/chai": "4.2.19",
-    "@types/chance": "1.1.2",
-    "@types/escodegen": "0.0.6",
-    "@types/eslint-scope": "3.7.0",
-    "@types/estraverse": "5.1.0",
-    "@types/estree": "0.0.48",
+    "@types/chai": "4.2.21",
+    "@types/chance": "1.1.3",
+    "@types/escodegen": "0.0.7",
+    "@types/eslint-scope": "3.7.1",
+    "@types/estraverse": "5.1.1",
+    "@types/estree": "0.0.50",
     "@types/js-string-escape": "1.0.0",
-    "@types/md5": "2.3.0",
-    "@types/mkdirp": "1.0.1",
-    "@types/mocha": "8.2.2",
+    "@types/md5": "2.3.1",
+    "@types/mkdirp": "1.0.2",
+    "@types/mocha": "8.2.3",
     "@types/multimatch": "4.0.0",
-    "@types/node": "15.12.4",
-    "@types/rimraf": "3.0.0",
+    "@types/node": "16.3.3",
+    "@types/rimraf": "3.0.1",
     "@types/sinon": "10.0.2",
     "@types/string-template": "1.0.2",
-    "@types/webpack-env": "1.16.0",
-    "@typescript-eslint/eslint-plugin": "4.28.0",
-    "@typescript-eslint/parser": "4.28.0",
+    "@types/webpack-env": "1.16.2",
+    "@typescript-eslint/eslint-plugin": "4.28.4",
+    "@typescript-eslint/parser": "4.28.4",
     "chai": "4.3.4",
     "chai-exclude": "2.0.3",
     "cross-env": "7.0.3",
-    "eslint": "7.29.0",
+    "eslint": "7.31.0",
     "eslint-plugin-import": "2.23.4",
-    "eslint-plugin-jsdoc": "35.4.0",
+    "eslint-plugin-jsdoc": "35.4.5",
     "eslint-plugin-no-null": "1.0.2",
     "eslint-plugin-prefer-arrow": "1.2.3",
-    "eslint-plugin-unicorn": "33.0.1",
+    "eslint-plugin-unicorn": "34.0.1",
     "fork-ts-checker-notifier-webpack-plugin": "4.0.0",
     "fork-ts-checker-webpack-plugin": "6.2.12",
-    "mocha": "9.0.1",
+    "mocha": "9.0.2",
     "nyc": "15.1.0",
     "pjson": "1.0.9",
     "pre-commit": "1.2.2",
     "rimraf": "3.0.2",
     "sinon": "11.1.1",
+    "source-map-resolve": "^0.6.0",
     "threads": "1.6.5",
     "ts-loader": "9.2.3",
-    "ts-node": "10.0.0",
-    "typescript": "4.3.4",
-    "webpack": "5.40.0",
+    "ts-node": "10.1.0",
+    "typescript": "4.3.5",
+    "webpack": "5.45.1",
     "webpack-cli": "4.7.2",
     "webpack-node-externals": "3.0.0"
   },

+ 15 - 11
src/JavaScriptObfuscator.ts

@@ -27,6 +27,7 @@ import { ecmaVersion } from './constants/EcmaVersion';
 import { ASTParserFacade } from './ASTParserFacade';
 import { NodeGuards } from './node/NodeGuards';
 import { Utils } from './utils/Utils';
+import { SourceMapSourcesMode } from './enums/source-map/SourceMapSourcesMode';
 
 @injectable()
 export class JavaScriptObfuscator implements IJavaScriptObfuscator {
@@ -247,20 +248,23 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
      */
     private generateCode (sourceCode: string, astTree: ESTree.Program): IGeneratorOutput {
         const escodegenParams: escodegen.GenerateOptions = {
-            ...JavaScriptObfuscator.escodegenParams
-        };
-
-        if (this.options.sourceMap) {
-            escodegenParams.sourceMap = this.options.inputFileName || 'sourceMap';
-            escodegenParams.sourceContent = sourceCode;
-        }
-
-        const generatorOutput: IGeneratorOutput = escodegen.generate(astTree, {
-            ...escodegenParams,
+            ...JavaScriptObfuscator.escodegenParams,
             format: {
                 compact: this.options.compact
+            },
+            ...this.options.sourceMap && {
+                ...this.options.sourceMapSourcesMode === SourceMapSourcesMode.SourcesContent
+                    ? {
+                        sourceMap: 'sourceMap',
+                        sourceContent: sourceCode
+                    }
+                    : {
+                        sourceMap: this.options.inputFileName
+                    }
             }
-        });
+        };
+
+        const generatorOutput: IGeneratorOutput = escodegen.generate(astTree, escodegenParams);
 
         generatorOutput.map = generatorOutput.map ? generatorOutput.map.toString() : '';
 

+ 10 - 3
src/cli/JavaScriptObfuscatorCLI.ts

@@ -17,6 +17,7 @@ import { ObfuscationTarget } from '../enums/ObfuscationTarget';
 import { OptionsPreset } from '../enums/options/presets/OptionsPreset';
 import { RenamePropertiesMode } from '../enums/node-transformers/rename-properties-transformers/RenamePropertiesMode';
 import { SourceMapMode } from '../enums/source-map/SourceMapMode';
+import { SourceMapSourcesMode } from '../enums/source-map/SourceMapSourcesMode';
 import { StringArrayEncoding } from '../enums/node-transformers/string-array-transformers/StringArrayEncoding';
 import { StringArrayIndexesType } from '../enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 import { StringArrayWrappersType } from '../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
@@ -53,10 +54,10 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
     public static readonly obfuscatedFilePrefix: string = '-obfuscated';
 
     /**
-     * @type {commander.CommanderStatic}
+     * @type {commander.Command}
      */
     @initializable()
-    private commands!: commander.CommanderStatic;
+    private commands!: commander.Command;
 
     /**
      * @type {IdentifierNamesCacheFileUtils}
@@ -144,7 +145,7 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
     }
 
     public initialize (): void {
-        this.commands = <commander.CommanderStatic>(new commander.Command());
+        this.commands = new commander.Command();
 
         this.configureCommands();
         this.configureHelp();
@@ -352,6 +353,12 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
                 `Values: ${CLIUtils.stringifyOptionAvailableValues(SourceMapMode)}. ` +
                 `Default: ${SourceMapMode.Separate}`
             )
+            .option(
+                '--source-map-sources-mode <string>',
+                'Specify source map sources mode. ' +
+                `Values: ${CLIUtils.stringifyOptionAvailableValues(SourceMapSourcesMode)}. ` +
+                `Default: ${SourceMapSourcesMode.SourcesContent}`
+            )
             .option(
                 '--split-strings <boolean>',
                 'Splits literal strings into chunks with length of `splitStringsChunkLength` option value',

+ 13 - 3
src/cli/utils/ObfuscatedCodeFileUtils.ts

@@ -98,9 +98,19 @@ export class ObfuscatedCodeFileUtils {
 
         if (sourceMapFileName) {
             const indexOfLastSeparator: number = normalizedOutputCodePath.lastIndexOf(path.sep);
-            const sourceMapPath: string = parsedOutputCodePath.ext && indexOfLastSeparator > 0
-                ? normalizedOutputCodePath.slice(0, indexOfLastSeparator)
-                : normalizedOutputCodePath;
+            let sourceMapPath: string;
+
+            if (parsedOutputCodePath.ext) {
+                // File path with directory, like: `foo/bar.js`, or without, like: `bar.js`
+                const isFilePathWithDirectory: boolean = indexOfLastSeparator > 0;
+
+                sourceMapPath = isFilePathWithDirectory
+                    ? normalizedOutputCodePath.slice(0, indexOfLastSeparator)
+                    : '';
+            } else {
+                sourceMapPath = normalizedOutputCodePath;
+            }
+
             // remove possible drive letter for win32 environment
             const normalizedSourceMapFilePath: string = sourceMapFileName.replace(/^[a-zA-Z]:\\*/, '');
 

+ 9 - 0
src/enums/source-map/SourceMapSourcesMode.ts

@@ -0,0 +1,9 @@
+import { Utils } from '../../utils/Utils';
+
+export const SourceMapSourcesMode: Readonly<{
+    Sources: 'sources';
+    SourcesContent: 'sources-content';
+}> = Utils.makeEnum({
+    Sources: 'sources',
+    SourcesContent: 'sources-content'
+});

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

@@ -9,6 +9,7 @@ import { TTypeFromEnum } from '../../types/utils/TTypeFromEnum';
 import { IdentifierNamesGenerator } from '../../enums/generators/identifier-names-generators/IdentifierNamesGenerator';
 import { ObfuscationTarget } from '../../enums/ObfuscationTarget';
 import { SourceMapMode } from '../../enums/source-map/SourceMapMode';
+import { SourceMapSourcesMode } from '../../enums/source-map/SourceMapSourcesMode';
 
 export interface IOptions {
     readonly compact: boolean;
@@ -45,6 +46,7 @@ export interface IOptions {
     readonly sourceMapBaseUrl: string;
     readonly sourceMapFileName: string;
     readonly sourceMapMode: TTypeFromEnum<typeof SourceMapMode>;
+    readonly sourceMapSourcesMode: TTypeFromEnum<typeof SourceMapSourcesMode>;
     readonly splitStrings: boolean;
     readonly splitStringsChunkLength: number;
     readonly stringArray: boolean;

+ 16 - 0
src/interfaces/source-code/ISourceMap.ts

@@ -0,0 +1,16 @@
+export interface ISourceMap {
+    /**
+     * @type {string}
+     */
+    mappings: string;
+
+    /**
+     * @type {string[]}
+     */
+    sources: string[];
+
+    /**
+     * @type {string[]}
+     */
+    sourcesContent: string[];
+}

+ 4 - 1
src/node/NodeFactory.ts

@@ -183,13 +183,16 @@ export class NodeFactory {
 
     /**
      * @param {Literal} source
+     * @param {Identifier | null} exported
      * @returns {ExportAllDeclaration}
      */
     public static exportAllDeclarationNode (
-        source: ESTree.Literal
+        source: ESTree.Literal,
+        exported: ESTree.Identifier | null
     ): ESTree.ExportAllDeclaration {
         return {
             type: NodeType.ExportAllDeclaration,
+            exported,
             source,
             metadata: { ignoredNode: false }
         };

+ 7 - 0
src/options/Options.ts

@@ -35,6 +35,7 @@ import { ObfuscationTarget } from '../enums/ObfuscationTarget';
 import { OptionsPreset } from '../enums/options/presets/OptionsPreset';
 import { RenamePropertiesMode } from '../enums/node-transformers/rename-properties-transformers/RenamePropertiesMode';
 import { SourceMapMode } from '../enums/source-map/SourceMapMode';
+import { SourceMapSourcesMode } from '../enums/source-map/SourceMapSourcesMode';
 import { StringArrayIndexesType } from '../enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 import { StringArrayEncoding } from '../enums/node-transformers/string-array-transformers/StringArrayEncoding';
 import { StringArrayWrappersType } from '../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
@@ -312,6 +313,12 @@ export class Options implements IOptions {
     @IsIn([SourceMapMode.Inline, SourceMapMode.Separate])
     public readonly sourceMapMode!: TTypeFromEnum<typeof SourceMapMode>;
 
+    /**
+     * @type {SourceMapSourcesMode}
+     */
+    @IsIn([SourceMapSourcesMode.Sources, SourceMapSourcesMode.SourcesContent])
+    public readonly sourceMapSourcesMode!: TTypeFromEnum<typeof SourceMapSourcesMode>;
+
     /**
      * @type {boolean}
      */

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

@@ -5,6 +5,7 @@ import { ObfuscationTarget } from '../../enums/ObfuscationTarget';
 import { OptionsPreset } from '../../enums/options/presets/OptionsPreset';
 import { RenamePropertiesMode } from '../../enums/node-transformers/rename-properties-transformers/RenamePropertiesMode';
 import { SourceMapMode } from '../../enums/source-map/SourceMapMode';
+import { SourceMapSourcesMode } from '../../enums/source-map/SourceMapSourcesMode';
 import { StringArrayIndexesType } from '../../enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 import { StringArrayEncoding } from '../../enums/node-transformers/string-array-transformers/StringArrayEncoding';
 import { StringArrayWrappersType } from '../../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
@@ -46,6 +47,7 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     sourceMapBaseUrl: '',
     sourceMapFileName: '',
     sourceMapMode: SourceMapMode.Separate,
+    sourceMapSourcesMode: SourceMapSourcesMode.SourcesContent,
     splitStrings: false,
     splitStringsChunkLength: 10,
     stringArray: true,

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

@@ -4,6 +4,7 @@ import { IdentifierNamesGenerator } from '../../enums/generators/identifier-name
 import { ObfuscationTarget } from '../../enums/ObfuscationTarget';
 import { RenamePropertiesMode } from '../../enums/node-transformers/rename-properties-transformers/RenamePropertiesMode';
 import { SourceMapMode } from '../../enums/source-map/SourceMapMode';
+import { SourceMapSourcesMode } from '../../enums/source-map/SourceMapSourcesMode';
 import { StringArrayEncoding } from '../../enums/node-transformers/string-array-transformers/StringArrayEncoding';
 import { StringArrayWrappersType } from '../../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 import { StringArrayIndexesType } from '../../enums/node-transformers/string-array-transformers/StringArrayIndexesType';
@@ -42,6 +43,7 @@ export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({
     sourceMapBaseUrl: '',
     sourceMapFileName: '',
     sourceMapMode: SourceMapMode.Separate,
+    sourceMapSourcesMode: SourceMapSourcesMode.SourcesContent,
     splitStrings: false,
     splitStringsChunkLength: 0,
     stringArray: false,

+ 2 - 1
test/declarations/index.d.ts

@@ -1,2 +1,3 @@
 /// <reference path="../../src/declarations/escodegen.d.ts" />
-/// <reference path="../../src/declarations/ESTree.d.ts" />
+/// <reference path="../../src/declarations/ESTree.d.ts" />
+/// <reference path="./source-map-resolve.d.ts" />

+ 19 - 0
test/declarations/source-map-resolve.d.ts

@@ -0,0 +1,19 @@
+declare module 'source-map-resolve' {
+    interface ExistingRawSourceMap {
+        mappings: string;
+        sources: string[];
+        sourcesContent: string[];
+    }
+
+    export interface ResolvedSources {
+        sourcesResolved: string[];
+        sourcesContent: (string | Error)[];
+    }
+
+    export function resolveSources(
+        map: ExistingRawSourceMap,
+        mapUrl: string,
+        read: (path: string, callback: (error: Error | null, data: Buffer | string) => void) => void,
+        callback: (error: Error | null, result: ResolvedSources) => void,
+    ): void;
+}

+ 384 - 64
test/functional-tests/cli/JavaScriptObfuscatorCLI.spec.ts

@@ -3,12 +3,16 @@ import * as mkdirp from 'mkdirp';
 import * as path from 'path';
 import * as rimraf from 'rimraf';
 import * as sinon from 'sinon';
+import { resolveSources } from 'source-map-resolve';
 
 import { assert } from 'chai';
 
+import { ISourceMap } from '../../../src/interfaces/source-code/ISourceMap';
+
 import { StdoutWriteMock } from '../../mocks/StdoutWriteMock';
 
 import { JavaScriptObfuscatorCLI } from '../../../src/JavaScriptObfuscatorCLIFacade';
+import { parseSourceMapFromObfuscatedCode } from '../../helpers/parseSourceMapFromObfuscatedCode';
 
 describe('JavaScriptObfuscatorCLI', function (): void {
     this.timeout(100000);
@@ -530,12 +534,12 @@ describe('JavaScriptObfuscatorCLI', function (): void {
 
             describe('Variant #1: `--sourceMapMode` option value is `separate`', () => {
                 describe('Variant #1: default behaviour', () => {
-                    const expectedSourceMapSourceName: string = path.basename(fixtureFileName);
-
                     let isFileExist: boolean,
-                        sourceMapObject: any;
+                        resolvedSources: string,
+                        sourceCodeContent: string,
+                        sourceMapObject: ISourceMap;
 
-                    before(() => {
+                    before((done) => {
                         JavaScriptObfuscatorCLI.obfuscate([
                             'node',
                             'javascript-obfuscator',
@@ -551,10 +555,18 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         ]);
 
                         try {
-                            const content: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' });
+                            sourceCodeContent = fs.readFileSync(fixtureFilePath, { encoding: 'utf8' });
+                            const sourceMapContent: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' });
 
                             isFileExist = true;
-                            sourceMapObject = JSON.parse(content);
+                            sourceMapObject = JSON.parse(sourceMapContent);
+
+                            resolveSources(sourceMapObject, fixtureFilePath, fs.readFile, (error, result) => {
+                                resolvedSources = typeof result.sourcesContent[0] === 'string'
+                                    ? result.sourcesContent[0]
+                                    : '';
+                                done();
+                            });
                         } catch (e) {
                             isFileExist = false;
                         }
@@ -568,18 +580,14 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         assert.property(sourceMapObject, 'version');
                     });
 
-                    it('source map from created file should contains property `sources`', () => {
-                        assert.property(sourceMapObject, 'sources');
-                    });
-
-                    it('source map source should has correct name', () => {
-                        assert.equal(sourceMapObject.sources[0], expectedSourceMapSourceName);
-                    });
-
                     it('source map from created file should contains property `names`', () => {
                         assert.property(sourceMapObject, 'names');
                     });
 
+                    it('should resolve correct sources from source map', () => {
+                        assert.equal(resolvedSources, sourceCodeContent);
+                    });
+
                     after(() => {
                         rimraf.sync(outputFilePath);
                         rimraf.sync(outputSourceMapPath);
@@ -587,12 +595,12 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                 });
 
                 describe('Variant #2: `sourceMapBaseUrl` option is set', () => {
-                    const expectedSourceMapSourceName: string = path.basename(fixtureFileName);
-
                     let isFileExist: boolean,
-                        sourceMapObject: any;
+                        resolvedSources: string,
+                        sourceCodeContent: string,
+                        sourceMapObject: ISourceMap;
 
-                    before(() => {
+                    before((done) => {
                         JavaScriptObfuscatorCLI.obfuscate([
                             'node',
                             'javascript-obfuscator',
@@ -610,10 +618,18 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         ]);
 
                         try {
-                            const content: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' });
+                            sourceCodeContent = fs.readFileSync(fixtureFilePath, { encoding: 'utf8' });
+                            const sourceMapContent: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' });
 
                             isFileExist = true;
-                            sourceMapObject = JSON.parse(content);
+                            sourceMapObject = JSON.parse(sourceMapContent);
+
+                            resolveSources(sourceMapObject, fixtureFilePath, fs.readFile, (error, result) => {
+                                resolvedSources = typeof result.sourcesContent[0] === 'string'
+                                    ? result.sourcesContent[0]
+                                    : '';
+                                done();
+                            });
                         } catch (e) {
                             isFileExist = false;
                         }
@@ -627,18 +643,14 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         assert.property(sourceMapObject, 'version');
                     });
 
-                    it('source map from created file should contains property `sources`', () => {
-                        assert.property(sourceMapObject, 'sources');
-                    });
-
-                    it('source map source should has correct name', () => {
-                        assert.equal(sourceMapObject.sources[0], expectedSourceMapSourceName);
-                    });
-
                     it('source map from created file should contains property `names`', () => {
                         assert.property(sourceMapObject, 'names');
                     });
 
+                    it('should resolve correct sources from source map', () => {
+                        assert.equal(resolvedSources, sourceCodeContent);
+                    });
+
                     after(() => {
                         fs.unlinkSync(outputFilePath);
                         fs.unlinkSync(outputSourceMapPath);
@@ -646,15 +658,16 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                 });
 
                 describe('Variant #3: `--sourceMapFileName` option is set', () => {
-                    const expectedSourceMapSourceName: string = path.basename(fixtureFileName);
                     const sourceMapFileName: string = 'test';
                     const sourceMapFilePath: string = `${sourceMapFileName}.js.map`;
                     const outputSourceMapFilePath: string = path.join(outputDirName, sourceMapFilePath);
 
                     let isFileExist: boolean,
-                        sourceMapObject: any;
+                        resolvedSources: string,
+                        sourceCodeContent: string,
+                        sourceMapObject: ISourceMap;
 
-                    before(() => {
+                    before((done) => {
                         JavaScriptObfuscatorCLI.obfuscate([
                             'node',
                             'javascript-obfuscator',
@@ -672,10 +685,18 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         ]);
 
                         try {
-                            const content: string = fs.readFileSync(outputSourceMapFilePath, { encoding: 'utf8' });
+                            sourceCodeContent = fs.readFileSync(fixtureFilePath, { encoding: 'utf8' });
+                            const sourceMapContent: string = fs.readFileSync(outputSourceMapFilePath, { encoding: 'utf8' });
 
                             isFileExist = true;
-                            sourceMapObject = JSON.parse(content);
+                            sourceMapObject = JSON.parse(sourceMapContent);
+
+                            resolveSources(sourceMapObject, fixtureFilePath, fs.readFile, (error, result) => {
+                                resolvedSources = typeof result.sourcesContent[0] === 'string'
+                                    ? result.sourcesContent[0]
+                                    : '';
+                                done();
+                            });
                         } catch (e) {
                             isFileExist = false;
                         }
@@ -689,54 +710,353 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         assert.property(sourceMapObject, 'version');
                     });
 
-                    it('source map from created file should contains property `sources`', () => {
-                        assert.property(sourceMapObject, 'sources');
-                    });
-
-                    it('source map source should has correct name', () => {
-                        assert.equal(sourceMapObject.sources[0], expectedSourceMapSourceName);
-                    });
-
                     it('source map from created file should contains property `names`', () => {
                         assert.property(sourceMapObject, 'names');
                     });
 
+                    it('should resolve correct sources from source map', () => {
+                        assert.equal(resolvedSources, sourceCodeContent);
+                    });
+
                     after(() => {
                         fs.unlinkSync(outputFilePath);
                         fs.unlinkSync(outputSourceMapFilePath);
                     });
                 });
+
+                describe('Variant #4: `sourceMapSourcesMode` option is set', () => {
+                    describe('Variant #1: `sourcesContent` value', () => {
+                        const expectedSourceMapSourceName: string = 'sourceMap';
+
+                        let isFileExist: boolean,
+                            resolvedSources: string,
+                            sourceCodeContent: string,
+                            sourceMapObject: ISourceMap;
+
+                        before((done) => {
+                            JavaScriptObfuscatorCLI.obfuscate([
+                                'node',
+                                'javascript-obfuscator',
+                                fixtureFilePath,
+                                '--output',
+                                outputFilePath,
+                                '--compact',
+                                'true',
+                                '--self-defending',
+                                '0',
+                                '--source-map',
+                                'true',
+                                '--source-map-sources-mode',
+                                'sources-content'
+                            ]);
+
+                            try {
+                                sourceCodeContent = fs.readFileSync(fixtureFilePath, { encoding: 'utf8' });
+                                const sourceMapContent: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' });
+
+                                isFileExist = true;
+                                sourceMapObject = JSON.parse(sourceMapContent);
+
+                                resolveSources(sourceMapObject, fixtureFilePath, fs.readFile, (error, result) => {
+                                    resolvedSources = typeof result.sourcesContent[0] === 'string'
+                                        ? result.sourcesContent[0]
+                                        : '';
+                                    done();
+                                });
+                            } catch (e) {
+                                isFileExist = false;
+                            }
+                        });
+
+                        it('should create file with source map in the same directory as output file', () => {
+                            assert.equal(isFileExist, true);
+                        });
+
+                        it('source map from created file should contains property `sources`', () => {
+                            assert.property(sourceMapObject, 'sources');
+                        });
+
+                        it('source map from created file should contains property `sourcesContent`', () => {
+                            assert.property(sourceMapObject, 'sourcesContent');
+                        });
+
+                        it('source map source should has correct sources', () => {
+                            assert.equal(sourceMapObject.sources[0], expectedSourceMapSourceName);
+                        });
+
+                        it('source map source should has correct sources content', () => {
+                            assert.equal(sourceMapObject.sourcesContent[0], sourceCodeContent);
+                        });
+
+                        it('should resolve correct sources from source map', () => {
+                            assert.equal(resolvedSources, sourceCodeContent);
+                        });
+
+                        after(() => {
+                            rimraf.sync(outputFilePath);
+                            rimraf.sync(outputSourceMapPath);
+                        });
+                    });
+
+                    describe('Variant #2: `sources` value', () => {
+                        const expectedSourceMapSourceName: string = path.basename(fixtureFileName);
+
+                        let isFileExist: boolean,
+                            resolvedSources: string,
+                            sourceCodeContent: string,
+                            sourceMapObject: ISourceMap;
+
+                        before((done) => {
+                            JavaScriptObfuscatorCLI.obfuscate([
+                                'node',
+                                'javascript-obfuscator',
+                                fixtureFilePath,
+                                '--output',
+                                outputFilePath,
+                                '--compact',
+                                'true',
+                                '--self-defending',
+                                '0',
+                                '--source-map',
+                                'true',
+                                '--source-map-sources-mode',
+                                'sources'
+                            ]);
+
+                            try {
+                                sourceCodeContent = fs.readFileSync(fixtureFilePath, { encoding: 'utf8' });
+                                const sourceMapContent: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' });
+
+                                isFileExist = true;
+                                sourceMapObject = JSON.parse(sourceMapContent);
+
+                                resolveSources(sourceMapObject, fixtureFilePath, fs.readFile, (error, result) => {
+                                    resolvedSources = typeof result.sourcesContent[0] === 'string'
+                                        ? result.sourcesContent[0]
+                                        : '';
+                                    done();
+                                });
+                            } catch (e) {
+                                isFileExist = false;
+                            }
+                        });
+
+                        it('should create file with source map in the same directory as output file', () => {
+                            assert.equal(isFileExist, true);
+                        });
+
+                        it('source map from created file should contains property `sources`', () => {
+                            assert.property(sourceMapObject, 'sources');
+                        });
+
+                        it('source map from created file should not contains property `sourcesContent`', () => {
+                            assert.notProperty(sourceMapObject, 'sourcesContent');
+                        });
+
+                        it('source map source should has correct sources', () => {
+                            assert.equal(sourceMapObject.sources[0], expectedSourceMapSourceName);
+                        });
+
+                        it('should resolve correct sources from source map', () => {
+                            assert.equal(resolvedSources, sourceCodeContent);
+                        });
+
+                        after(() => {
+                            rimraf.sync(outputFilePath);
+                            rimraf.sync(outputSourceMapPath);
+                        });
+                    });
+                });
             });
 
             describe('Variant #2: `--sourceMapMode` option is `inline`', () => {
-                let isFileExist: boolean;
+                describe('Variant #1: default behaviour', () => {
+                    let isFileExist: boolean,
+                        resolvedSources: string,
+                        sourceCodeContent: string,
+                        sourceMapObject: ISourceMap;
 
-                before(() => {
-                    JavaScriptObfuscatorCLI.obfuscate([
-                        'node',
-                        'javascript-obfuscator',
-                        fixtureFilePath,
-                        '--output',
-                        outputFilePath,
-                        '--compact',
-                        'true',
-                        '--self-defending',
-                        '0',
-                        '--source-map',
-                        'true',
-                        '--source-map-mode',
-                        'inline'
-                    ]);
+                    before((done) => {
+                        JavaScriptObfuscatorCLI.obfuscate([
+                            'node',
+                            'javascript-obfuscator',
+                            fixtureFilePath,
+                            '--output',
+                            outputFilePath,
+                            '--compact',
+                            'true',
+                            '--self-defending',
+                            '0',
+                            '--source-map',
+                            'true',
+                            '--source-map-mode',
+                            'inline'
+                        ]);
 
-                    isFileExist = fs.existsSync(outputSourceMapPath);
-                });
+                        isFileExist = fs.existsSync(outputSourceMapPath);
+
+                        sourceCodeContent = fs.readFileSync(fixtureFilePath, { encoding: 'utf8' });
+                        const obfuscatedCodeContent = fs.readFileSync(outputFilePath, { encoding: 'utf8' });
+
+                        sourceMapObject = parseSourceMapFromObfuscatedCode(obfuscatedCodeContent);
+
+                        resolveSources(sourceMapObject, fixtureFilePath, fs.readFile, (error, result) => {
+                            resolvedSources = typeof result.sourcesContent[0] === 'string'
+                                ? result.sourcesContent[0]
+                                : '';
+                            done();
+                        });
+                    });
+
+                    it('shouldn\'t create file with source map', () => {
+                        assert.equal(isFileExist, false);
+                    });
+
+                    it('should resolve correct sources from source map', () => {
+                        assert.equal(resolvedSources, sourceCodeContent);
+                    });
 
-                it('shouldn\'t create file with source map', () => {
-                    assert.equal(isFileExist, false);
+                    after(() => {
+                        fs.unlinkSync(outputFilePath);
+                    });
                 });
 
-                after(() => {
-                    fs.unlinkSync(outputFilePath);
+                describe('Variant #2: `sourceMapSourcesMode` option is set', () => {
+                    describe('Variant #1: `sourcesContent` value', () => {
+                        const expectedSourceMapSourceName: string = 'sourceMap';
+
+                        let isFileExist: boolean,
+                            resolvedSources: string,
+                            sourceCodeContent: string,
+                            sourceMapObject: ISourceMap;
+
+                        before((done) => {
+                            JavaScriptObfuscatorCLI.obfuscate([
+                                'node',
+                                'javascript-obfuscator',
+                                fixtureFilePath,
+                                '--output',
+                                outputFilePath,
+                                '--compact',
+                                'true',
+                                '--self-defending',
+                                '0',
+                                '--source-map',
+                                'true',
+                                '--source-map-mode',
+                                'inline',
+                                '--source-map-sources-mode',
+                                'sources-content'
+                            ]);
+
+                            isFileExist = fs.existsSync(outputSourceMapPath);
+
+                            sourceCodeContent = fs.readFileSync(fixtureFilePath, { encoding: 'utf8' });
+                            const obfuscatedCodeContent = fs.readFileSync(outputFilePath, { encoding: 'utf8' });
+                            sourceMapObject = parseSourceMapFromObfuscatedCode(obfuscatedCodeContent);
+
+                            resolveSources(sourceMapObject, fixtureFilePath, fs.readFile, (error, result) => {
+                                resolvedSources = typeof result.sourcesContent[0] === 'string'
+                                    ? result.sourcesContent[0]
+                                    : '';
+                                done();
+                            });
+                        });
+
+                        it('shouldn\'t create file with source map', () => {
+                            assert.equal(isFileExist, false);
+                        });
+
+                        it('source map from created file should contains property `sources`', () => {
+                            assert.property(sourceMapObject, 'sources');
+                        });
+
+                        it('source map from created file should contains property `sourcesContent`', () => {
+                            assert.property(sourceMapObject, 'sourcesContent');
+                        });
+
+                        it('source map source should has correct sources', () => {
+                            assert.equal(sourceMapObject.sources[0], expectedSourceMapSourceName);
+                        });
+
+                        it('source map source should has correct sources content', () => {
+                            assert.equal(sourceMapObject.sourcesContent[0], sourceCodeContent);
+                        });
+
+                        it('should resolve correct sources from source map', () => {
+                            assert.equal(resolvedSources, sourceCodeContent);
+                        });
+
+                        after(() => {
+                            fs.unlinkSync(outputFilePath);
+                        });
+                    });
+
+                    describe('Variant #2: `sources` value', () => {
+                        const expectedSourceMapSourceName: string = path.basename(fixtureFileName);
+
+                        let isFileExist: boolean,
+                            resolvedSources: string,
+                            sourceCodeContent: string,
+                            sourceMapObject: ISourceMap;
+
+                        before((done) => {
+                            JavaScriptObfuscatorCLI.obfuscate([
+                                'node',
+                                'javascript-obfuscator',
+                                fixtureFilePath,
+                                '--output',
+                                outputFilePath,
+                                '--compact',
+                                'true',
+                                '--self-defending',
+                                '0',
+                                '--source-map',
+                                'true',
+                                '--source-map-mode',
+                                'inline',
+                                '--source-map-sources-mode',
+                                'sources'
+                            ]);
+
+                            isFileExist = fs.existsSync(outputSourceMapPath);
+
+                            sourceCodeContent = fs.readFileSync(fixtureFilePath, { encoding: 'utf8' });
+                            const obfuscatedCodeContent = fs.readFileSync(outputFilePath, { encoding: 'utf8' });
+                            sourceMapObject = parseSourceMapFromObfuscatedCode(obfuscatedCodeContent);
+
+                            resolveSources(sourceMapObject, fixtureFilePath, fs.readFile, (error, result) => {
+                                resolvedSources = typeof result.sourcesContent[0] === 'string'
+                                    ? result.sourcesContent[0]
+                                    : '';
+                                done();
+                            });
+                        });
+
+                        it('shouldn\'t create file with source map', () => {
+                            assert.equal(isFileExist, false);
+                        });
+
+                        it('source map from created file should contains property `sources`', () => {
+                            assert.property(sourceMapObject, 'sources');
+                        });
+
+                        it('source map from created file should contains property `sourcesContent`', () => {
+                            assert.notProperty(sourceMapObject, 'sourcesContent');
+                        });
+
+                        it('source map source should has correct sources', () => {
+                            assert.equal(sourceMapObject.sources[0], expectedSourceMapSourceName);
+                        });
+
+                        it('should resolve correct sources from source map', () => {
+                            assert.equal(resolvedSources, sourceCodeContent);
+                        });
+
+                        after(() => {
+                            fs.unlinkSync(outputFilePath);
+                        });
+                    });
                 });
             });
         });

+ 58 - 14
test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts

@@ -1,4 +1,6 @@
+import * as fs from 'fs';
 import { assert } from 'chai';
+import { resolveSources } from 'source-map-resolve';
 
 import { TDictionary } from '../../../src/types/TDictionary';
 import { TIdentifierNamesCache } from '../../../src/types/TIdentifierNamesCache';
@@ -7,6 +9,7 @@ import { TOptionsPreset } from '../../../src/types/options/TOptionsPreset';
 import { TTypeFromEnum } from '../../../src/types/utils/TTypeFromEnum';
 
 import { IObfuscationResult } from '../../../src/interfaces/source-code/IObfuscationResult';
+import { ISourceMap } from '../../../src/interfaces/source-code/ISourceMap';
 
 import { SourceMapMode } from '../../../src/enums/source-map/SourceMapMode';
 import { StringArrayEncoding } from '../../../src/enums/node-transformers/string-array-transformers/StringArrayEncoding';
@@ -107,11 +110,13 @@ describe('JavaScriptObfuscator', () => {
 
         describe('`sourceMap` option is `true`', () => {
             describe('`sourceMapMode` is `separate`', () => {
-                let obfuscatedCode: string,
-                    sourceMap: string;
+                let code: string,
+                    obfuscatedCode: string,
+                    sourceMap: ISourceMap,
+                    resolvedSources: string;
 
-                beforeEach(() => {
-                    const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
+                beforeEach((done) => {
+                    code = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
                     const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         {
@@ -121,26 +126,46 @@ describe('JavaScriptObfuscator', () => {
                     );
 
                     obfuscatedCode = obfuscationResult.getObfuscatedCode();
-                    sourceMap = JSON.parse(obfuscationResult.getSourceMap()).mappings;
+                    sourceMap = JSON.parse(obfuscationResult.getSourceMap());
+                    resolveSources(sourceMap, '/', fs.readFile, (error, result) => {
+                        resolvedSources = typeof result.sourcesContent[0] === 'string'
+                            ? result.sourcesContent[0]
+                            : '';
+                        done();
+                    });
                 });
 
                 it('should return correct obfuscated code', () => {
                     assert.isOk(obfuscatedCode);
                 });
 
-                it('should return correct source map', () => {
-                    assert.isOk(sourceMap);
+                it('should return correct source map mappings', () => {
+                    assert.isOk(sourceMap.mappings);
+                });
+
+                it('should define placeholder `sources` field for source map', () => {
+                    assert.deepEqual(sourceMap.sources, ['sourceMap']);
+                });
+
+                it('should define `sourcesContent` field for source map', () => {
+                    assert.isOk(sourceMap.sourcesContent);
+                });
+
+                it('should resolve correct sources from source map', () => {
+                    assert.equal(resolvedSources, code);
                 });
             });
 
             describe('`sourceMapMode` is `inline`', () => {
                 const regExp: RegExp = /sourceMappingURL=data:application\/json;base64/;
 
-                let obfuscatedCode: string,
-                    sourceMap: string;
+                let code: string,
+                    obfuscatedCode: string,
+                    sourceMap: ISourceMap,
+                    resolvedSources: string;
 
-                beforeEach(() => {
-                    const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
+                beforeEach((done) => {
+                    code = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
                     const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         {
@@ -151,7 +176,13 @@ describe('JavaScriptObfuscator', () => {
                     );
 
                     obfuscatedCode = obfuscationResult.getObfuscatedCode();
-                    sourceMap = JSON.parse(obfuscationResult.getSourceMap()).mappings;
+                    sourceMap = JSON.parse(obfuscationResult.getSourceMap());
+                    resolveSources(sourceMap, '/', fs.readFile, (error, result) => {
+                        resolvedSources = typeof result.sourcesContent[0] === 'string'
+                            ? result.sourcesContent[0]
+                            : '';
+                        done();
+                    });
                 });
 
                 it('should return correct obfuscated code', () => {
@@ -162,8 +193,21 @@ describe('JavaScriptObfuscator', () => {
                     assert.match(obfuscatedCode, regExp);
                 });
 
-                it('should return correct source map', () => {
-                    assert.isOk(sourceMap);
+                it('should return correct source map mappings', () => {
+                    assert.isOk(sourceMap.mappings);
+                });
+
+                it('should define placeholder `sources` field for source map', () => {
+                    assert.deepEqual(sourceMap.sources, ['sourceMap']);
+                });
+
+                it('should define `sourcesContent` field for source map', () => {
+                    assert.isOk(sourceMap.sourcesContent);
+                });
+
+                it('should resolve correct sources from source map', () => {
+                    console.log(resolvedSources);
+                    assert.equal(resolvedSources, code);
                 });
             });
 

+ 15 - 0
test/helpers/cryptUtilsAtob.ts

@@ -0,0 +1,15 @@
+/**
+ * @param {string} encodedString
+ * @returns {string}
+ */
+export function cryptUtilsAtob (encodedString: string): string {
+    const baseDecodedString = atob(encodedString);
+
+    var tempDecodedString = '';
+
+    for (let k = 0, length = baseDecodedString.length; k < length; k++) {
+        tempDecodedString += '%' + ('00' + baseDecodedString.charCodeAt(k).toString(16)).slice(-2);
+    }
+
+    return decodeURIComponent(tempDecodedString);
+}

+ 11 - 0
test/helpers/parseSourceMapFromObfuscatedCode.ts

@@ -0,0 +1,11 @@
+import { ISourceMap } from '../../src/interfaces/source-code/ISourceMap';
+
+import { cryptUtilsAtob } from './cryptUtilsAtob';
+
+/**
+ * @param {string} obfuscatedCodeWithInlineSourceMap
+ * @returns {ISourceMap}
+ */
+export function parseSourceMapFromObfuscatedCode (obfuscatedCodeWithInlineSourceMap: string): ISourceMap {
+    return JSON.parse(cryptUtilsAtob(obfuscatedCodeWithInlineSourceMap.split('base64,')[1]));
+}

+ 304 - 190
test/unit-tests/cli/utils/ObfuscatedCodeFileUtils.spec.ts

@@ -307,202 +307,316 @@ describe('obfuscatedCodeFileUtils', () => {
 
     describe('getOutputSourceMapPath', () => {
         describe('Variant #1: output code path is a file path', () => {
-            const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
-            const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
-            const outputCodePath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
-            const expectedOutputSourceMapPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js.map');
-
-            let outputSourceMapPath: string;
-
-            before(() => {
-                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
-                    rawInputPath,
-                    {
-                        output: rawOutputPath
-                    }
-                );
-                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath);
-            });
-
-            it('should return output path for source map', () => {
-                assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
-            });
-        });
-
-        describe('Variant #2: output code path is a directory path and source map file name is not set', () => {
-            const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
-            const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
-            const outputCodePath: string = path.join(tmpDirectoryPath, 'output');
-
-            let testFunc: () => string;
-
-            before(() => {
-                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
-                    rawInputPath,
-                    {
-                        output: rawOutputPath
-                    }
-                );
-                testFunc = () => obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath);
-            });
-
-            it('should throw an error if output code path is a directory path and source map file name is not set', () => {
-                assert.throws(testFunc, Error);
-            });
-        });
-
-        describe('Variant #3: output code path with dot', () => {
-            const rawInputPath: string = path.join(tmpDirectoryPath, 'input.with.dot', 'test-input.js');
-            const rawOutputPath: string = path.join(tmpDirectoryPath, 'output.with.dot', 'test-output.js');
-            const outputCodePath: string = path.join(tmpDirectoryPath, 'output.with.dot', 'test-output.js');
-            const expectedOutputSourceMapPath: string = path.join(tmpDirectoryPath, 'output.with.dot', 'test-output.js.map');
-
-            let outputSourceMapPath: string;
+            describe('Variant #1: file path with directory', () => {
+                describe('Variant #1: source map file name is not set', () => {
+                    describe('Variant #1: base output code path', () => {
+                        const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
+                        const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
+                        const outputCodePath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
+                        const expectedOutputSourceMapPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js.map');
+
+                        let outputSourceMapPath: string;
+
+                        before(() => {
+                            const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
+                                rawInputPath,
+                                {
+                                    output: rawOutputPath
+                                }
+                            );
+                            outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath);
+                        });
+
+                        it('should return output path for source map', () => {
+                            assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
+                        });
+                    });
 
-            before(() => {
-                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
-                    rawInputPath,
-                    {
-                        output: rawOutputPath
-                    }
-                );
-                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath);
-            });
+                    describe('Variant #2: output code path with dot', () => {
+                        const rawInputPath: string = path.join(tmpDirectoryPath, 'input.with.dot', 'test-input.js');
+                        const rawOutputPath: string = path.join(tmpDirectoryPath, 'output.with.dot', 'test-output.js');
+                        const outputCodePath: string = path.join(tmpDirectoryPath, 'output.with.dot', 'test-output.js');
+                        const expectedOutputSourceMapPath: string = path.join(tmpDirectoryPath, 'output.with.dot', 'test-output.js.map');
+
+                        let outputSourceMapPath: string;
+
+                        before(() => {
+                            const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
+                                rawInputPath,
+                                {
+                                    output: rawOutputPath
+                                }
+                            );
+                            outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath);
+                        });
+
+                        it('should return output path for source map', () => {
+                            assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
+                        });
+                    });
+                });
 
-            it('should return output path for source map', () => {
-                assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
-            });
-        });
+                describe('Variant #2: source map file name is set', () => {
+                    describe('Variant #1: source map file name without extension is set', () => {
+                        const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
+                        const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
+                        const outputCodePath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
+                        const sourceMapFileName: string = 'foo';
+                        const expectedOutputSourceMapPath: string = path.join(tmpDirectoryPath, 'output', 'foo.js.map');
+
+                        let outputSourceMapPath: string;
+
+                        before(() => {
+                            const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
+                                rawInputPath,
+                                {
+                                    output: rawOutputPath
+                                }
+                            );
+                            outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                        });
+
+                        it('should return output path for source map', () => {
+                            assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
+                        });
+                    });
 
-        describe('Variant #4: source map file name without extension is set', () => {
-            const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
-            const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
-            const outputCodePath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
-            const sourceMapFileName: string = 'foo';
-            const expectedOutputSourceMapPath: string = path.join(tmpDirectoryPath, 'output', 'foo.js.map');
+                    describe('Variant #2: source map file name with wrong extension is set', () => {
+                        const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
+                        const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
+                        const outputCodePath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
+                        const sourceMapFileName: string = 'foo.js';
+                        const expectedOutputSourceMapPath: string = path.join(tmpDirectoryPath, 'output', 'foo.js.map');
+
+                        let outputSourceMapPath: string;
+
+                        before(() => {
+                            const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
+                                rawInputPath,
+                                {
+                                    output: rawOutputPath
+                                }
+                            );
+                            outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                        });
+
+                        it('should return output path for source map', () => {
+                            assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
+                        });
+                    });
 
-            let outputSourceMapPath: string;
+                    describe('Variant #3: source map file name with valid extension is set', () => {
+                        const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
+                        const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
+                        const outputCodePath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
+                        const sourceMapFileName: string = 'foo.js.map';
+                        const expectedOutputSourceMapPath: string = path.join(tmpDirectoryPath, 'output', 'foo.js.map');
+
+                        let outputSourceMapPath: string;
+
+                        before(() => {
+                            const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
+                                rawInputPath,
+                                {
+                                    output: rawOutputPath
+                                }
+                            );
+                            outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                        });
+
+                        it('should return output path for source map', () => {
+                            assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
+                        });
+                    });
 
-            before(() => {
-                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
-                    rawInputPath,
-                    {
-                        output: rawOutputPath
-                    }
-                );
-                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
-            });
+                    describe('Variant #4: source map file name contains directories', () => {
+                        const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
+                        const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
+                        const outputCodePath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
+                        const sourceMapFileName: string = path.join('parent', 'foo.js.map');
+                        const expectedOutputSourceMapPath: string = path.join(tmpDirectoryPath, 'output', 'parent', 'foo.js.map');
+
+                        let outputSourceMapPath: string;
+
+                        before(() => {
+                            const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
+                                rawInputPath,
+                                {
+                                    output: rawOutputPath
+                                }
+                            );
+                            outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                        });
+
+                        it('should return output path for source map', () => {
+                            assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
+                        });
+                    });
 
-            it('should return output path for source map', () => {
-                assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
+                    describe('Variant #5: output code path with dot', () => {
+                        const rawInputPath: string = path.join(tmpDirectoryPath, 'input.with.dot', 'test-input.js');
+                        const rawOutputPath: string = path.join(tmpDirectoryPath, 'output.with.dot', 'test-output.js');
+                        const outputCodePath: string = path.join(tmpDirectoryPath, 'output.with.dot', 'test-output.js');
+                        const sourceMapFileName: string = path.join('parent', 'foo.js.map');
+                        const expectedOutputSourceMapPath: string = path.join(tmpDirectoryPath, 'output.with.dot', 'parent', 'foo.js.map');
+
+                        let outputSourceMapPath: string;
+
+                        before(() => {
+                            const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
+                                rawInputPath,
+                                {
+                                    output: rawOutputPath
+                                }
+                            );
+                            outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                        });
+
+                        it('should return output path for source map', () => {
+                            assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
+                        });
+                    });
+                });
             });
-        });
-
-        describe('Variant #5: output code path is a directory path and source map file name without extension is set', () => {
-            const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
-            const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
-            const outputCodePath: string = path.join(tmpDirectoryPath, 'output');
-            const sourceMapFileName: string = 'foo';
-            const expectedOutputSourceMapPath: string = path.join(tmpDirectoryPath, 'output', 'foo.js.map');
 
-            let outputSourceMapPath: string;
+            describe('Variant #2: file path without directory', () => {
+                describe('Variant #1: source map file name is not set', () => {
+                    describe('Variant #1: base output code path', () => {
+                        const rawInputPath: string = 'test-input.js';
+                        const rawOutputPath: string = 'test-output.js';
+                        const outputCodePath: string = 'test-output.js';
+                        const expectedOutputSourceMapPath: string = 'test-output.js.map';
+
+                        let outputSourceMapPath: string;
+
+                        before(() => {
+                            const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
+                                rawInputPath,
+                                {
+                                    output: rawOutputPath
+                                }
+                            );
+                            outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath);
+                        });
+
+                        it('should return output path for source map', () => {
+                            assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
+                        });
+                    });
+                });
 
-            before(() => {
-                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
-                    rawInputPath,
-                    {
-                        output: rawOutputPath
-                    }
-                );
-                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                describe('Variant #2: source map file name is set', () => {
+                    describe('Variant #1: base output code path', () => {
+                        const rawInputPath: string = 'test-input.js';
+                        const rawOutputPath: string = 'test-output.js';
+                        const outputCodePath: string = 'test-output.js';
+                        const outputSourceMapFileName: string = 'test-output-source-map';
+                        const expectedOutputSourceMapPath: string = 'test-output-source-map.js.map';
+
+                        let outputSourceMapPath: string;
+
+                        before(() => {
+                            const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
+                                rawInputPath,
+                                {
+                                    output: rawOutputPath
+                                }
+                            );
+                            outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(
+                                outputCodePath,
+                                outputSourceMapFileName
+                            );
+                        });
+
+                        it('should return output path for source map', () => {
+                            assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
+                        });
+                    });
+                });
             });
 
-            it('should return output path for source map', () => {
-                assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
-            });
-        });
+            describe('Variant #10: Win32 environment', () => {
+                describe('Variant #1: source map file name is a file name without extension', () => {
+                    const rawInputPath: string = path.join('C:\\', tmpDirectoryPath, 'input', 'test-input.js');
+                    const rawOutputPath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'test-output.js');
+                    const outputCodePath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'test-output.js');
+                    const sourceMapFileName: string = path.join('foo');
+                    const expectedOutputSourceMapPath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'foo.js.map');
 
-        describe('Variant #6: source map file name with wrong extension is set', () => {
-            const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
-            const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
-            const outputCodePath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
-            const sourceMapFileName: string = 'foo.js';
-            const expectedOutputSourceMapPath: string = path.join(tmpDirectoryPath, 'output', 'foo.js.map');
+                    let outputSourceMapPath: string;
 
-            let outputSourceMapPath: string;
+                    before(() => {
+                        const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
+                            rawInputPath,
+                            {
+                                output: rawOutputPath
+                            }
+                        );
+                        outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                    });
 
-            before(() => {
-                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
-                    rawInputPath,
-                    {
-                        output: rawOutputPath
-                    }
-                );
-                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
-            });
+                    it('should return output path for source map', () => {
+                        assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
+                    });
+                });
 
-            it('should return output path for source map', () => {
-                assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
-            });
-        });
+                describe('Variant #2: source map file name is a file name with an extension', () => {
+                    const rawInputPath: string = path.join('C:\\', tmpDirectoryPath, 'input', 'test-input.js');
+                    const rawOutputPath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'test-output.js');
+                    const outputCodePath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'test-output.js');
+                    const sourceMapFileName: string = path.join('foo.js.map');
+                    const expectedOutputSourceMapPath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'foo.js.map');
 
-        describe('Variant #7: source map file name with valid extension is set', () => {
-            const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
-            const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
-            const outputCodePath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
-            const sourceMapFileName: string = 'foo.js.map';
-            const expectedOutputSourceMapPath: string = path.join(tmpDirectoryPath, 'output', 'foo.js.map');
-
-            let outputSourceMapPath: string;
+                    let outputSourceMapPath: string;
 
-            before(() => {
-                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
-                    rawInputPath,
-                    {
-                        output: rawOutputPath
-                    }
-                );
-                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
-            });
+                    before(() => {
+                        const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
+                            rawInputPath,
+                            {
+                                output: rawOutputPath
+                            }
+                        );
+                        outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                    });
 
-            it('should return output path for source map', () => {
-                assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
-            });
-        });
+                    it('should return output path for source map', () => {
+                        assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
+                    });
+                });
 
-        describe('Variant #8: source map file name is a path', () => {
-            const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
-            const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
-            const outputCodePath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
-            const sourceMapFileName: string = path.join('parent', 'foo.js.map');
-            const expectedOutputSourceMapPath: string = path.join(tmpDirectoryPath, 'output', 'parent', 'foo.js.map');
+                describe('Variant #3: output path and win32 path in source map file name', () => {
+                    const rawInputPath: string = path.join('C:\\', tmpDirectoryPath, 'input', 'test-input.js');
+                    const rawOutputPath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'test-output.js');
+                    const outputCodePath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'test-output.js');
+                    const sourceMapFileName: string = path.join('C:\\', 'parent', 'foo.js.map');
+                    const expectedOutputSourceMapPath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'parent', 'foo.js.map');
 
-            let outputSourceMapPath: string;
+                    let outputSourceMapPath: string;
 
-            before(() => {
-                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
-                    rawInputPath,
-                    {
-                        output: rawOutputPath
-                    }
-                );
-                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
-            });
+                    before(() => {
+                        const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
+                            rawInputPath,
+                            {
+                                output: rawOutputPath
+                            }
+                        );
+                        outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                    });
 
-            it('should return output path for source map', () => {
-                assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
+                    it('should return output path for source map', () => {
+                        assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
+                    });
+                });
             });
         });
 
-        describe('Variant #9: Win32 environment', () => {
-            describe('Variant #1: output code path is a directory path', () => {
-                const rawInputPath: string = path.join('C:\\', tmpDirectoryPath, 'input', 'test-input.js');
-                const rawOutputPath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'test-output.js');
-                const outputCodePath: string = path.join('C:\\', tmpDirectoryPath, 'output');
-                const sourceMapFileName: string = path.join('foo');
-                const expectedOutputSourceMapPath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'foo.js.map');
+        describe(`Variant #2: output code path is a directory path`, () => {
+            describe('Variant #1: source map file name is not set', () => {
+                const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
+                const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
+                const outputCodePath: string = path.join(tmpDirectoryPath, 'output');
 
-                let outputSourceMapPath: string;
+                let testFunc: () => string;
 
                 before(() => {
                     const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
@@ -511,20 +625,20 @@ describe('obfuscatedCodeFileUtils', () => {
                             output: rawOutputPath
                         }
                     );
-                    outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                    testFunc = () => obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath);
                 });
 
-                it('should return output path for source map', () => {
-                    assert.equal(outputSourceMapPath, expectedOutputSourceMapPath);
+                it('should throw an error if output code path is a directory path and source map file name is not set', () => {
+                    assert.throws(testFunc, Error);
                 });
             });
 
-            describe('Variant #2: source map file name is a file name without extension', () => {
-                const rawInputPath: string = path.join('C:\\', tmpDirectoryPath, 'input', 'test-input.js');
-                const rawOutputPath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'test-output.js');
-                const outputCodePath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'test-output.js');
-                const sourceMapFileName: string = path.join('foo');
-                const expectedOutputSourceMapPath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'foo.js.map');
+            describe('Variant #2: source map file name without extension is set', () => {
+                const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
+                const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
+                const outputCodePath: string = path.join(tmpDirectoryPath, 'output');
+                const sourceMapFileName: string = 'foo';
+                const expectedOutputSourceMapPath: string = path.join(tmpDirectoryPath, 'output', 'foo.js.map');
 
                 let outputSourceMapPath: string;
 
@@ -543,12 +657,12 @@ describe('obfuscatedCodeFileUtils', () => {
                 });
             });
 
-            describe('Variant #3: source map file name is a file name with an extension', () => {
-                const rawInputPath: string = path.join('C:\\', tmpDirectoryPath, 'input', 'test-input.js');
-                const rawOutputPath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'test-output.js');
-                const outputCodePath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'test-output.js');
-                const sourceMapFileName: string = path.join('foo.js.map');
-                const expectedOutputSourceMapPath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'foo.js.map');
+            describe('Variant #2: source map file name with extension is set', () => {
+                const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
+                const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
+                const outputCodePath: string = path.join(tmpDirectoryPath, 'output');
+                const sourceMapFileName: string = 'foo.js.map';
+                const expectedOutputSourceMapPath: string = path.join(tmpDirectoryPath, 'output', 'foo.js.map');
 
                 let outputSourceMapPath: string;
 
@@ -567,12 +681,12 @@ describe('obfuscatedCodeFileUtils', () => {
                 });
             });
 
-            describe('Variant #4: output path and win32 path in source map file name', () => {
+            describe('Variant #3: Win32 environment', () => {
                 const rawInputPath: string = path.join('C:\\', tmpDirectoryPath, 'input', 'test-input.js');
                 const rawOutputPath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'test-output.js');
-                const outputCodePath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'test-output.js');
-                const sourceMapFileName: string = path.join('C:\\', 'parent', 'foo.js.map');
-                const expectedOutputSourceMapPath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'parent', 'foo.js.map');
+                const outputCodePath: string = path.join('C:\\', tmpDirectoryPath, 'output');
+                const sourceMapFileName: string = path.join('foo');
+                const expectedOutputSourceMapPath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'foo.js.map');
 
                 let outputSourceMapPath: string;
 
@@ -592,7 +706,7 @@ describe('obfuscatedCodeFileUtils', () => {
             });
         });
 
-        describe('Variant #10: empty paths', () => {
+        describe('Variant #3: empty paths', () => {
             const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
             const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');
 

+ 2 - 1
test/unit-tests/node/node-literal-utils/NodeLiteralUtils.spec.ts

@@ -180,7 +180,8 @@ describe('NodeLiteralUtils', () => {
 
                     before(() => {
                         exportAllDeclarationNode = NodeFactory.exportAllDeclarationNode(
-                            literalNode
+                            literalNode,
+                            null
                         );
 
                         literalNode.parentNode = exportAllDeclarationNode;

+ 44 - 8
test/unit-tests/utils/CryptUtils.spec.ts

@@ -8,6 +8,7 @@ import { ICryptUtils } from '../../../src/interfaces/utils/ICryptUtils';
 import { IInversifyContainerFacade } from '../../../src/interfaces/container/IInversifyContainerFacade';
 
 import { InversifyContainerFacade } from '../../../src/container/InversifyContainerFacade';
+import { cryptUtilsAtob } from '../../helpers/cryptUtilsAtob';
 
 describe('CryptUtils', () => {
     let cryptUtils: ICryptUtils;
@@ -21,30 +22,65 @@ describe('CryptUtils', () => {
 
     describe('btoa', () => {
        describe('Variant #1: basic', () => {
-           const expectedString: string = 'c3RyaW5n';
+           const expectedEncodedString: string = 'c3RyaW5n';
+           const expectedDecodedString: string = 'string';
 
-           let string: string;
+           let encodedString: string,
+               decodedString: string;
 
            before(() => {
-               string = cryptUtils.btoa('string');
+               encodedString = cryptUtils.btoa('string');
+               decodedString = cryptUtilsAtob(encodedString);
            });
 
            it('should create a base-64 encoded string from a given string', () => {
-               assert.equal(string, expectedString);
+               assert.equal(encodedString, expectedEncodedString);
+           });
+
+           it('should create encoded string that can be successfully decoded', () => {
+               assert.equal(decodedString, expectedDecodedString);
            });
        });
 
         describe('Variant #2: padding characters', () => {
-            const expectedString: string = 'c3RyaQ==';
+            const expectedEncodedString: string = 'c3RyaQ==';
+            const expectedDecodedString: string = 'stri';
 
-            let string: string;
+            let encodedString: string,
+                decodedString: string;
 
             before(() => {
-                string = cryptUtils.btoa('stri');
+                encodedString = cryptUtils.btoa('stri');
+                decodedString = cryptUtilsAtob(encodedString);
             });
 
             it('should create a base-64 encoded string from a given string with padding characters', () => {
-                assert.equal(string, expectedString);
+                assert.equal(encodedString, expectedEncodedString);
+            });
+
+            it('should create encoded string that can be successfully decoded', () => {
+                assert.equal(decodedString, expectedDecodedString);
+            });
+        });
+
+        describe('Variant #3: cyrillic string', () => {
+            const expectedEncodedString: string = '0YLQtdGB0YI=';
+            const expectedDecodedString: string = 'тест';
+
+            let encodedString: string,
+                decodedString: string;
+
+            before(() => {
+                encodedString = cryptUtils.btoa('тест');
+                decodedString = cryptUtilsAtob(encodedString);
+            });
+
+            it('should create a base-64 encoded string from a given string', () => {
+                assert.equal(encodedString, expectedEncodedString);
+            });
+
+            it('should create encoded string with a cyrillic characters that can be successfully decoded', () => {
+                assert.equal(decodedString, expectedDecodedString);
             });
         });
     });

File diff suppressed because it is too large
+ 350 - 287
yarn.lock


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