Browse Source

Merge pull request #957 from javascript-obfuscator/source-maps-fixes

**New option**: `sourceMapSourcesMode`
Timofey Kachalov 3 years ago
parent
commit
1985c39d51

+ 6 - 0
CHANGELOG.md

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

+ 10 - 0
README.md

@@ -377,6 +377,7 @@ Following options are available for the JS Obfuscator:
     sourceMapBaseUrl: '',
     sourceMapBaseUrl: '',
     sourceMapFileName: '',
     sourceMapFileName: '',
     sourceMapMode: 'separate',
     sourceMapMode: 'separate',
+    sourceMapSourcesMode: 'sources-content',
     splitStrings: false,
     splitStrings: false,
     splitStringsChunkLength: 10,
     splitStringsChunkLength: 10,
     stringArray: true,
     stringArray: true,
@@ -438,6 +439,7 @@ Following options are available for the JS Obfuscator:
     --source-map-base-url <string>
     --source-map-base-url <string>
     --source-map-file-name <string>
     --source-map-file-name <string>
     --source-map-mode <string> [inline, separate]
     --source-map-mode <string> [inline, separate]
+    --source-map-sources-mode <string> [sources, sources-content]
     --split-strings <boolean>
     --split-strings <boolean>
     --split-strings-chunk-length <number>
     --split-strings-chunk-length <number>
     --string-array <boolean>
     --string-array <boolean>
@@ -840,6 +842,7 @@ Prevents obfuscation of `require` imports. Could be helpful in some cases when f
 Type: `string` Default: `''`
 Type: `string` Default: `''`
 
 
 Allows to set name of the input file with source code. This name will be used internally for source map generation.
 Allows to set name of the input file with source code. This name will be used internally for source map generation.
+Required when using NodeJS API and `sourceMapSourcesMode` option has `sources` value`.
 
 
 ### `log`
 ### `log`
 Type: `boolean` Default: `false`
 Type: `boolean` Default: `false`
@@ -1071,6 +1074,13 @@ Specifies source map generation mode:
 * `inline` - add source map at the end of each .js files;
 * `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`.
 * `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. When using NodeJS API it's required to define `inputFileName` option that will be used as `sources` field value.
+
 ### `splitStrings`
 ### `splitStrings`
 Type: `boolean` Default: `false`
 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",
   "name": "javascript-obfuscator",
-  "version": "2.16.0",
+  "version": "2.17.0",
   "description": "JavaScript obfuscator",
   "description": "JavaScript obfuscator",
   "keywords": [
   "keywords": [
     "obfuscator",
     "obfuscator",
@@ -24,12 +24,12 @@
     "@javascript-obfuscator/escodegen": "2.2.0",
     "@javascript-obfuscator/escodegen": "2.2.0",
     "@javascript-obfuscator/estraverse": "5.3.0",
     "@javascript-obfuscator/estraverse": "5.3.0",
     "@nuxtjs/opencollective": "0.3.2",
     "@nuxtjs/opencollective": "0.3.2",
-    "acorn": "8.4.0",
+    "acorn": "8.4.1",
     "assert": "2.0.0",
     "assert": "2.0.0",
     "chalk": "4.1.1",
     "chalk": "4.1.1",
     "chance": "1.1.7",
     "chance": "1.1.7",
     "class-validator": "0.13.1",
     "class-validator": "0.13.1",
-    "commander": "7.2.0",
+    "commander": "8.0.0",
     "eslint-scope": "5.1.1",
     "eslint-scope": "5.1.1",
     "fast-deep-equal": "3.1.3",
     "fast-deep-equal": "3.1.3",
     "inversify": "5.1.1",
     "inversify": "5.1.1",
@@ -46,46 +46,47 @@
   },
   },
   "devDependencies": {
   "devDependencies": {
     "@istanbuljs/nyc-config-typescript": "1.0.1",
     "@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/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/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/sinon": "10.0.2",
     "@types/string-template": "1.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": "4.3.4",
     "chai-exclude": "2.0.3",
     "chai-exclude": "2.0.3",
     "cross-env": "7.0.3",
     "cross-env": "7.0.3",
-    "eslint": "7.29.0",
+    "eslint": "7.31.0",
     "eslint-plugin-import": "2.23.4",
     "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-no-null": "1.0.2",
     "eslint-plugin-prefer-arrow": "1.2.3",
     "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-notifier-webpack-plugin": "4.0.0",
     "fork-ts-checker-webpack-plugin": "6.2.12",
     "fork-ts-checker-webpack-plugin": "6.2.12",
-    "mocha": "9.0.1",
+    "mocha": "9.0.2",
     "nyc": "15.1.0",
     "nyc": "15.1.0",
     "pjson": "1.0.9",
     "pjson": "1.0.9",
     "pre-commit": "1.2.2",
     "pre-commit": "1.2.2",
     "rimraf": "3.0.2",
     "rimraf": "3.0.2",
     "sinon": "11.1.1",
     "sinon": "11.1.1",
+    "source-map-resolve": "^0.6.0",
     "threads": "1.6.5",
     "threads": "1.6.5",
     "ts-loader": "9.2.3",
     "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-cli": "4.7.2",
     "webpack-node-externals": "3.0.0"
     "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 { ASTParserFacade } from './ASTParserFacade';
 import { NodeGuards } from './node/NodeGuards';
 import { NodeGuards } from './node/NodeGuards';
 import { Utils } from './utils/Utils';
 import { Utils } from './utils/Utils';
+import { SourceMapSourcesMode } from './enums/source-map/SourceMapSourcesMode';
 
 
 @injectable()
 @injectable()
 export class JavaScriptObfuscator implements IJavaScriptObfuscator {
 export class JavaScriptObfuscator implements IJavaScriptObfuscator {
@@ -247,20 +248,23 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
      */
      */
     private generateCode (sourceCode: string, astTree: ESTree.Program): IGeneratorOutput {
     private generateCode (sourceCode: string, astTree: ESTree.Program): IGeneratorOutput {
         const escodegenParams: escodegen.GenerateOptions = {
         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: {
             format: {
                 compact: this.options.compact
                 compact: this.options.compact
+            },
+            ...this.options.sourceMap && {
+                ...this.options.sourceMapSourcesMode === SourceMapSourcesMode.SourcesContent
+                    ? {
+                        sourceMap: 'sourceMap',
+                        sourceContent: sourceCode
+                    }
+                    : {
+                        sourceMap: this.options.inputFileName || 'sourceMap'
+                    }
             }
             }
-        });
+        };
+
+        const generatorOutput: IGeneratorOutput = escodegen.generate(astTree, escodegenParams);
 
 
         generatorOutput.map = generatorOutput.map ? generatorOutput.map.toString() : '';
         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 { OptionsPreset } from '../enums/options/presets/OptionsPreset';
 import { RenamePropertiesMode } from '../enums/node-transformers/rename-properties-transformers/RenamePropertiesMode';
 import { RenamePropertiesMode } from '../enums/node-transformers/rename-properties-transformers/RenamePropertiesMode';
 import { SourceMapMode } from '../enums/source-map/SourceMapMode';
 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 { StringArrayEncoding } from '../enums/node-transformers/string-array-transformers/StringArrayEncoding';
 import { StringArrayIndexesType } from '../enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 import { StringArrayIndexesType } from '../enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 import { StringArrayWrappersType } from '../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 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';
     public static readonly obfuscatedFilePrefix: string = '-obfuscated';
 
 
     /**
     /**
-     * @type {commander.CommanderStatic}
+     * @type {commander.Command}
      */
      */
     @initializable()
     @initializable()
-    private commands!: commander.CommanderStatic;
+    private commands!: commander.Command;
 
 
     /**
     /**
      * @type {IdentifierNamesCacheFileUtils}
      * @type {IdentifierNamesCacheFileUtils}
@@ -144,7 +145,7 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
     }
     }
 
 
     public initialize (): void {
     public initialize (): void {
-        this.commands = <commander.CommanderStatic>(new commander.Command());
+        this.commands = new commander.Command();
 
 
         this.configureCommands();
         this.configureCommands();
         this.configureHelp();
         this.configureHelp();
@@ -352,6 +353,12 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
                 `Values: ${CLIUtils.stringifyOptionAvailableValues(SourceMapMode)}. ` +
                 `Values: ${CLIUtils.stringifyOptionAvailableValues(SourceMapMode)}. ` +
                 `Default: ${SourceMapMode.Separate}`
                 `Default: ${SourceMapMode.Separate}`
             )
             )
+            .option(
+                '--source-map-sources-mode <string>',
+                'Specify source map sources mode. ' +
+                `Values: ${CLIUtils.stringifyOptionAvailableValues(SourceMapSourcesMode)}. ` +
+                `Default: ${SourceMapSourcesMode.SourcesContent}`
+            )
             .option(
             .option(
                 '--split-strings <boolean>',
                 '--split-strings <boolean>',
                 'Splits literal strings into chunks with length of `splitStringsChunkLength` option value',
                 '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) {
         if (sourceMapFileName) {
             const indexOfLastSeparator: number = normalizedOutputCodePath.lastIndexOf(path.sep);
             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
             // remove possible drive letter for win32 environment
             const normalizedSourceMapFilePath: string = sourceMapFileName.replace(/^[a-zA-Z]:\\*/, '');
             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 { IdentifierNamesGenerator } from '../../enums/generators/identifier-names-generators/IdentifierNamesGenerator';
 import { ObfuscationTarget } from '../../enums/ObfuscationTarget';
 import { ObfuscationTarget } from '../../enums/ObfuscationTarget';
 import { SourceMapMode } from '../../enums/source-map/SourceMapMode';
 import { SourceMapMode } from '../../enums/source-map/SourceMapMode';
+import { SourceMapSourcesMode } from '../../enums/source-map/SourceMapSourcesMode';
 
 
 export interface IOptions {
 export interface IOptions {
     readonly compact: boolean;
     readonly compact: boolean;
@@ -45,6 +46,7 @@ export interface IOptions {
     readonly sourceMapBaseUrl: string;
     readonly sourceMapBaseUrl: string;
     readonly sourceMapFileName: string;
     readonly sourceMapFileName: string;
     readonly sourceMapMode: TTypeFromEnum<typeof SourceMapMode>;
     readonly sourceMapMode: TTypeFromEnum<typeof SourceMapMode>;
+    readonly sourceMapSourcesMode: TTypeFromEnum<typeof SourceMapSourcesMode>;
     readonly splitStrings: boolean;
     readonly splitStrings: boolean;
     readonly splitStringsChunkLength: number;
     readonly splitStringsChunkLength: number;
     readonly stringArray: boolean;
     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 {Literal} source
+     * @param {Identifier | null} exported
      * @returns {ExportAllDeclaration}
      * @returns {ExportAllDeclaration}
      */
      */
     public static exportAllDeclarationNode (
     public static exportAllDeclarationNode (
-        source: ESTree.Literal
+        source: ESTree.Literal,
+        exported: ESTree.Identifier | null
     ): ESTree.ExportAllDeclaration {
     ): ESTree.ExportAllDeclaration {
         return {
         return {
             type: NodeType.ExportAllDeclaration,
             type: NodeType.ExportAllDeclaration,
+            exported,
             source,
             source,
             metadata: { ignoredNode: false }
             metadata: { ignoredNode: false }
         };
         };

+ 9 - 1
src/options/Options.ts

@@ -35,6 +35,7 @@ import { ObfuscationTarget } from '../enums/ObfuscationTarget';
 import { OptionsPreset } from '../enums/options/presets/OptionsPreset';
 import { OptionsPreset } from '../enums/options/presets/OptionsPreset';
 import { RenamePropertiesMode } from '../enums/node-transformers/rename-properties-transformers/RenamePropertiesMode';
 import { RenamePropertiesMode } from '../enums/node-transformers/rename-properties-transformers/RenamePropertiesMode';
 import { SourceMapMode } from '../enums/source-map/SourceMapMode';
 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 { StringArrayIndexesType } from '../enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 import { StringArrayEncoding } from '../enums/node-transformers/string-array-transformers/StringArrayEncoding';
 import { StringArrayEncoding } from '../enums/node-transformers/string-array-transformers/StringArrayEncoding';
 import { StringArrayWrappersType } from '../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 import { StringArrayWrappersType } from '../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
@@ -48,6 +49,7 @@ import { ValidationErrorsFormatter } from './ValidationErrorsFormatter';
 import { IsAllowedForObfuscationTargets } from './validators/IsAllowedForObfuscationTargets';
 import { IsAllowedForObfuscationTargets } from './validators/IsAllowedForObfuscationTargets';
 import { IsDomainLockRedirectUrl } from './validators/IsDomainLockRedirectUrl';
 import { IsDomainLockRedirectUrl } from './validators/IsDomainLockRedirectUrl';
 import { IsIdentifierNamesCache } from './validators/IsIdentifierNamesCache';
 import { IsIdentifierNamesCache } from './validators/IsIdentifierNamesCache';
+import { IsInputFileName } from './validators/IsInputFileName';
 
 
 @injectable()
 @injectable()
 export class Options implements IOptions {
 export class Options implements IOptions {
@@ -194,7 +196,7 @@ export class Options implements IOptions {
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
-    @IsString()
+    @IsInputFileName()
     public readonly inputFileName!: string;
     public readonly inputFileName!: string;
 
 
     /**
     /**
@@ -312,6 +314,12 @@ export class Options implements IOptions {
     @IsIn([SourceMapMode.Inline, SourceMapMode.Separate])
     @IsIn([SourceMapMode.Inline, SourceMapMode.Separate])
     public readonly sourceMapMode!: TTypeFromEnum<typeof SourceMapMode>;
     public readonly sourceMapMode!: TTypeFromEnum<typeof SourceMapMode>;
 
 
+    /**
+     * @type {SourceMapSourcesMode}
+     */
+    @IsIn([SourceMapSourcesMode.Sources, SourceMapSourcesMode.SourcesContent])
+    public readonly sourceMapSourcesMode!: TTypeFromEnum<typeof SourceMapSourcesMode>;
+
     /**
     /**
      * @type {boolean}
      * @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 { OptionsPreset } from '../../enums/options/presets/OptionsPreset';
 import { RenamePropertiesMode } from '../../enums/node-transformers/rename-properties-transformers/RenamePropertiesMode';
 import { RenamePropertiesMode } from '../../enums/node-transformers/rename-properties-transformers/RenamePropertiesMode';
 import { SourceMapMode } from '../../enums/source-map/SourceMapMode';
 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 { StringArrayIndexesType } from '../../enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 import { StringArrayEncoding } from '../../enums/node-transformers/string-array-transformers/StringArrayEncoding';
 import { StringArrayEncoding } from '../../enums/node-transformers/string-array-transformers/StringArrayEncoding';
 import { StringArrayWrappersType } from '../../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 import { StringArrayWrappersType } from '../../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
@@ -46,6 +47,7 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     sourceMapBaseUrl: '',
     sourceMapBaseUrl: '',
     sourceMapFileName: '',
     sourceMapFileName: '',
     sourceMapMode: SourceMapMode.Separate,
     sourceMapMode: SourceMapMode.Separate,
+    sourceMapSourcesMode: SourceMapSourcesMode.SourcesContent,
     splitStrings: false,
     splitStrings: false,
     splitStringsChunkLength: 10,
     splitStringsChunkLength: 10,
     stringArray: true,
     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 { ObfuscationTarget } from '../../enums/ObfuscationTarget';
 import { RenamePropertiesMode } from '../../enums/node-transformers/rename-properties-transformers/RenamePropertiesMode';
 import { RenamePropertiesMode } from '../../enums/node-transformers/rename-properties-transformers/RenamePropertiesMode';
 import { SourceMapMode } from '../../enums/source-map/SourceMapMode';
 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 { StringArrayEncoding } from '../../enums/node-transformers/string-array-transformers/StringArrayEncoding';
 import { StringArrayWrappersType } from '../../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 import { StringArrayWrappersType } from '../../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 import { StringArrayIndexesType } from '../../enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 import { StringArrayIndexesType } from '../../enums/node-transformers/string-array-transformers/StringArrayIndexesType';
@@ -42,6 +43,7 @@ export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({
     sourceMapBaseUrl: '',
     sourceMapBaseUrl: '',
     sourceMapFileName: '',
     sourceMapFileName: '',
     sourceMapMode: SourceMapMode.Separate,
     sourceMapMode: SourceMapMode.Separate,
+    sourceMapSourcesMode: SourceMapSourcesMode.SourcesContent,
     splitStrings: false,
     splitStrings: false,
     splitStringsChunkLength: 0,
     splitStringsChunkLength: 0,
     stringArray: false,
     stringArray: false,

+ 18 - 0
src/options/validators/IsInputFileName.ts

@@ -0,0 +1,18 @@
+import { IsNotEmpty, IsString, ValidateIf } from 'class-validator';
+
+import { TInputOptions } from '../../types/options/TInputOptions';
+
+import { SourceMapSourcesMode } from '../../enums/source-map/SourceMapSourcesMode';
+
+/**
+ * @returns {PropertyDecorator}
+ */
+export const IsInputFileName = (): PropertyDecorator => {
+    return (target: any, key: string | symbol): void => {
+        IsString()(target, key);
+        ValidateIf(({sourceMapSourcesMode}: TInputOptions) => {
+            return sourceMapSourcesMode === SourceMapSourcesMode.Sources;
+        })(target, key);
+        IsNotEmpty()(target, key);
+    };
+};

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

@@ -1,2 +1,3 @@
 /// <reference path="../../src/declarations/escodegen.d.ts" />
 /// <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 path from 'path';
 import * as rimraf from 'rimraf';
 import * as rimraf from 'rimraf';
 import * as sinon from 'sinon';
 import * as sinon from 'sinon';
+import { resolveSources } from 'source-map-resolve';
 
 
 import { assert } from 'chai';
 import { assert } from 'chai';
 
 
+import { ISourceMap } from '../../../src/interfaces/source-code/ISourceMap';
+
 import { StdoutWriteMock } from '../../mocks/StdoutWriteMock';
 import { StdoutWriteMock } from '../../mocks/StdoutWriteMock';
 
 
 import { JavaScriptObfuscatorCLI } from '../../../src/JavaScriptObfuscatorCLIFacade';
 import { JavaScriptObfuscatorCLI } from '../../../src/JavaScriptObfuscatorCLIFacade';
+import { parseSourceMapFromObfuscatedCode } from '../../helpers/parseSourceMapFromObfuscatedCode';
 
 
 describe('JavaScriptObfuscatorCLI', function (): void {
 describe('JavaScriptObfuscatorCLI', function (): void {
     this.timeout(100000);
     this.timeout(100000);
@@ -530,12 +534,12 @@ describe('JavaScriptObfuscatorCLI', function (): void {
 
 
             describe('Variant #1: `--sourceMapMode` option value is `separate`', () => {
             describe('Variant #1: `--sourceMapMode` option value is `separate`', () => {
                 describe('Variant #1: default behaviour', () => {
                 describe('Variant #1: default behaviour', () => {
-                    const expectedSourceMapSourceName: string = path.basename(fixtureFileName);
-
                     let isFileExist: boolean,
                     let isFileExist: boolean,
-                        sourceMapObject: any;
+                        resolvedSources: string,
+                        sourceCodeContent: string,
+                        sourceMapObject: ISourceMap;
 
 
-                    before(() => {
+                    before((done) => {
                         JavaScriptObfuscatorCLI.obfuscate([
                         JavaScriptObfuscatorCLI.obfuscate([
                             'node',
                             'node',
                             'javascript-obfuscator',
                             'javascript-obfuscator',
@@ -551,10 +555,18 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         ]);
                         ]);
 
 
                         try {
                         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;
                             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) {
                         } catch (e) {
                             isFileExist = false;
                             isFileExist = false;
                         }
                         }
@@ -568,18 +580,14 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         assert.property(sourceMapObject, 'version');
                         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`', () => {
                     it('source map from created file should contains property `names`', () => {
                         assert.property(sourceMapObject, 'names');
                         assert.property(sourceMapObject, 'names');
                     });
                     });
 
 
+                    it('should resolve correct sources from source map', () => {
+                        assert.equal(resolvedSources, sourceCodeContent);
+                    });
+
                     after(() => {
                     after(() => {
                         rimraf.sync(outputFilePath);
                         rimraf.sync(outputFilePath);
                         rimraf.sync(outputSourceMapPath);
                         rimraf.sync(outputSourceMapPath);
@@ -587,12 +595,12 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                 });
                 });
 
 
                 describe('Variant #2: `sourceMapBaseUrl` option is set', () => {
                 describe('Variant #2: `sourceMapBaseUrl` option is set', () => {
-                    const expectedSourceMapSourceName: string = path.basename(fixtureFileName);
-
                     let isFileExist: boolean,
                     let isFileExist: boolean,
-                        sourceMapObject: any;
+                        resolvedSources: string,
+                        sourceCodeContent: string,
+                        sourceMapObject: ISourceMap;
 
 
-                    before(() => {
+                    before((done) => {
                         JavaScriptObfuscatorCLI.obfuscate([
                         JavaScriptObfuscatorCLI.obfuscate([
                             'node',
                             'node',
                             'javascript-obfuscator',
                             'javascript-obfuscator',
@@ -610,10 +618,18 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         ]);
                         ]);
 
 
                         try {
                         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;
                             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) {
                         } catch (e) {
                             isFileExist = false;
                             isFileExist = false;
                         }
                         }
@@ -627,18 +643,14 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         assert.property(sourceMapObject, 'version');
                         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`', () => {
                     it('source map from created file should contains property `names`', () => {
                         assert.property(sourceMapObject, 'names');
                         assert.property(sourceMapObject, 'names');
                     });
                     });
 
 
+                    it('should resolve correct sources from source map', () => {
+                        assert.equal(resolvedSources, sourceCodeContent);
+                    });
+
                     after(() => {
                     after(() => {
                         fs.unlinkSync(outputFilePath);
                         fs.unlinkSync(outputFilePath);
                         fs.unlinkSync(outputSourceMapPath);
                         fs.unlinkSync(outputSourceMapPath);
@@ -646,15 +658,16 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                 });
                 });
 
 
                 describe('Variant #3: `--sourceMapFileName` option is set', () => {
                 describe('Variant #3: `--sourceMapFileName` option is set', () => {
-                    const expectedSourceMapSourceName: string = path.basename(fixtureFileName);
                     const sourceMapFileName: string = 'test';
                     const sourceMapFileName: string = 'test';
                     const sourceMapFilePath: string = `${sourceMapFileName}.js.map`;
                     const sourceMapFilePath: string = `${sourceMapFileName}.js.map`;
                     const outputSourceMapFilePath: string = path.join(outputDirName, sourceMapFilePath);
                     const outputSourceMapFilePath: string = path.join(outputDirName, sourceMapFilePath);
 
 
                     let isFileExist: boolean,
                     let isFileExist: boolean,
-                        sourceMapObject: any;
+                        resolvedSources: string,
+                        sourceCodeContent: string,
+                        sourceMapObject: ISourceMap;
 
 
-                    before(() => {
+                    before((done) => {
                         JavaScriptObfuscatorCLI.obfuscate([
                         JavaScriptObfuscatorCLI.obfuscate([
                             'node',
                             'node',
                             'javascript-obfuscator',
                             'javascript-obfuscator',
@@ -672,10 +685,18 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         ]);
                         ]);
 
 
                         try {
                         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;
                             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) {
                         } catch (e) {
                             isFileExist = false;
                             isFileExist = false;
                         }
                         }
@@ -689,54 +710,353 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         assert.property(sourceMapObject, 'version');
                         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`', () => {
                     it('source map from created file should contains property `names`', () => {
                         assert.property(sourceMapObject, 'names');
                         assert.property(sourceMapObject, 'names');
                     });
                     });
 
 
+                    it('should resolve correct sources from source map', () => {
+                        assert.equal(resolvedSources, sourceCodeContent);
+                    });
+
                     after(() => {
                     after(() => {
                         fs.unlinkSync(outputFilePath);
                         fs.unlinkSync(outputFilePath);
                         fs.unlinkSync(outputSourceMapFilePath);
                         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`', () => {
             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);
+                        });
+                    });
                 });
                 });
             });
             });
         });
         });

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

@@ -1,4 +1,6 @@
+import * as fs from 'fs';
 import { assert } from 'chai';
 import { assert } from 'chai';
+import { resolveSources } from 'source-map-resolve';
 
 
 import { TDictionary } from '../../../src/types/TDictionary';
 import { TDictionary } from '../../../src/types/TDictionary';
 import { TIdentifierNamesCache } from '../../../src/types/TIdentifierNamesCache';
 import { TIdentifierNamesCache } from '../../../src/types/TIdentifierNamesCache';
@@ -7,8 +9,10 @@ import { TOptionsPreset } from '../../../src/types/options/TOptionsPreset';
 import { TTypeFromEnum } from '../../../src/types/utils/TTypeFromEnum';
 import { TTypeFromEnum } from '../../../src/types/utils/TTypeFromEnum';
 
 
 import { IObfuscationResult } from '../../../src/interfaces/source-code/IObfuscationResult';
 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 { SourceMapMode } from '../../../src/enums/source-map/SourceMapMode';
+import { SourceMapSourcesMode } from '../../../src/enums/source-map/SourceMapSourcesMode';
 import { StringArrayEncoding } from '../../../src/enums/node-transformers/string-array-transformers/StringArrayEncoding';
 import { StringArrayEncoding } from '../../../src/enums/node-transformers/string-array-transformers/StringArrayEncoding';
 import { StringArrayIndexesType } from '../../../src/enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 import { StringArrayIndexesType } from '../../../src/enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 import { StringArrayWrappersType } from '../../../src/enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 import { StringArrayWrappersType } from '../../../src/enums/node-transformers/string-array-transformers/StringArrayWrappersType';
@@ -107,11 +111,13 @@ describe('JavaScriptObfuscator', () => {
 
 
         describe('`sourceMap` option is `true`', () => {
         describe('`sourceMap` option is `true`', () => {
             describe('`sourceMapMode` is `separate`', () => {
             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(
                     const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         code,
                         {
                         {
@@ -121,26 +127,46 @@ describe('JavaScriptObfuscator', () => {
                     );
                     );
 
 
                     obfuscatedCode = obfuscationResult.getObfuscatedCode();
                     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', () => {
                 it('should return correct obfuscated code', () => {
                     assert.isOk(obfuscatedCode);
                     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`', () => {
             describe('`sourceMapMode` is `inline`', () => {
                 const regExp: RegExp = /sourceMappingURL=data:application\/json;base64/;
                 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(
                     const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         code,
                         {
                         {
@@ -151,7 +177,13 @@ describe('JavaScriptObfuscator', () => {
                     );
                     );
 
 
                     obfuscatedCode = obfuscationResult.getObfuscatedCode();
                     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', () => {
                 it('should return correct obfuscated code', () => {
@@ -162,8 +194,21 @@ describe('JavaScriptObfuscator', () => {
                     assert.match(obfuscatedCode, regExp);
                     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);
                 });
                 });
             });
             });
 
 
@@ -207,6 +252,128 @@ describe('JavaScriptObfuscator', () => {
                     assert.isNotOk(sourceMapMappings);
                     assert.isNotOk(sourceMapMappings);
                 });
                 });
             });
             });
+
+            describe('`sourceMapSourceMode` is set', () => {
+                describe('`sourcesContent` value', () => {
+                    let code: string,
+                        obfuscatedCode: string,
+                        sourceMap: ISourceMap,
+                        resolvedSources: string;
+
+                    beforeEach((done) => {
+                        code = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
+                        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                sourceMap: true,
+                                sourceMapSourcesMode: SourceMapSourcesMode.SourcesContent
+                            }
+                        );
+
+                        obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                        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 mappings', () => {
+                        assert.isOk(sourceMap.mappings);
+                    });
+
+                    it('should define `sources` field for source map', () => {
+                        assert.property(sourceMap, 'sources');
+                    });
+
+                    it('should define `sourcesContent` field for source map', () => {
+                        assert.property(sourceMap, 'sourcesContent');
+                    });
+
+                    it('should define placeholder `sources` field for source map', () => {
+                        assert.deepEqual(sourceMap.sources, ['sourceMap']);
+                    });
+
+                    it('should define `sourcesContent` value with source code', () => {
+                        assert.deepEqual(sourceMap.sourcesContent[0], code);
+                    });
+
+                    it('should resolve correct sources from source map', () => {
+                        assert.equal(resolvedSources, code);
+                    });
+                });
+
+                describe('`sources` value', () => {
+                    describe('`inputFileName` option is set', () => {
+                        let code: string,
+                            obfuscatedCode: string,
+                            sourceMap: ISourceMap;
+
+                        beforeEach(() => {
+                            code = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
+                            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                                code,
+                                {
+                                    ...NO_ADDITIONAL_NODES_PRESET,
+                                    inputFileName: 'someFile.js',
+                                    sourceMap: true,
+                                    sourceMapSourcesMode: SourceMapSourcesMode.Sources
+                                }
+                            );
+
+                            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                            sourceMap = JSON.parse(obfuscationResult.getSourceMap());
+                        });
+
+                        it('should return correct obfuscated code', () => {
+                            assert.isOk(obfuscatedCode);
+                        });
+
+                        it('should return correct source map mappings', () => {
+                            assert.isOk(sourceMap.mappings);
+                        });
+
+                        it('should define `sources` field for source map', () => {
+                            assert.property(sourceMap, 'sources');
+                        });
+
+                        it('should not define `sourcesContent` field for source map', () => {
+                            assert.notProperty(sourceMap, 'sourcesContent');
+                        });
+
+                        it('should define placeholder `sources` field for source map', () => {
+                            assert.deepEqual(sourceMap.sources, ['someFile.js']);
+                        });
+                    })
+
+                    describe('`inputFileName` option is not set', () => {
+                        let testFunc: () => IObfuscationResult;
+
+                        beforeEach(() => {
+                            const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
+                            testFunc = () => JavaScriptObfuscator.obfuscate(
+                                code,
+                                {
+                                    ...NO_ADDITIONAL_NODES_PRESET,
+                                    sourceMap: true,
+                                    sourceMapSourcesMode: SourceMapSourcesMode.Sources
+                                }
+                            );
+                        });
+
+                        it('should throw an error', () => {
+                            assert.throws(testFunc, Error);
+                        });
+                    });
+                });
+            });
         });
         });
 
 
         describe('variable inside global scope', () => {
         describe('variable inside global scope', () => {

+ 72 - 0
test/functional-tests/options/input-file-name/Validation.spec.ts

@@ -0,0 +1,72 @@
+import { assert } from 'chai';
+
+import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscatorFacade';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
+import { SourceMapSourcesMode } from '../../../../src/enums/source-map/SourceMapSourcesMode';
+
+describe('`inputFileName` validation', () => {
+    describe('IsInputFileName', () => {
+        describe('Variant #1: positive validation', () => {
+            describe('Variant #1: empty string when `sourceMapSourcesMode: \'sources-content\'', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            inputFileName: '',
+                            sourceMapSourcesMode: SourceMapSourcesMode.SourcesContent
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should pass validation', () => {
+                    assert.doesNotThrow(testFunc);
+                });
+            });
+
+            describe('Variant #2: string with input file name when `sourceMapSourcesMode: \'sources\'', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            inputFileName: 'some-file.js',
+                            sourceMapSourcesMode: SourceMapSourcesMode.Sources
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should pass validation', () => {
+                    assert.doesNotThrow(testFunc);
+                });
+            });
+        });
+
+        describe('Variant #2: negative validation', () => {
+            describe('Variant #1: empty string when `sourceMapSourcesMode: \'sources\'', () => {
+                const expectedError: string = 'should not be empty';
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            inputFileName: '',
+                            sourceMapSourcesMode: SourceMapSourcesMode.Sources
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not pass validation', () => {
+                    assert.throws(testFunc, expectedError);
+                });
+            });
+        });
+    });
+});

+ 7 - 0
test/helpers/atob.ts

@@ -0,0 +1,7 @@
+/**
+ * @param {string} encodedString
+ * @returns {string}
+ */
+export function atob (encodedString: string): string {
+    return Buffer.from(encodedString, 'base64').toString();
+}

+ 11 - 0
test/helpers/parseSourceMapFromObfuscatedCode.ts

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

+ 1 - 0
test/index.spec.ts

@@ -136,6 +136,7 @@ import './functional-tests/options/OptionsNormalizer.spec';
 import './functional-tests/options/domain-lock-destination/Validation.spec';
 import './functional-tests/options/domain-lock-destination/Validation.spec';
 import './functional-tests/options/domain-lock/Validation.spec';
 import './functional-tests/options/domain-lock/Validation.spec';
 import './functional-tests/options/identifier-names-cache/Validation.spec';
 import './functional-tests/options/identifier-names-cache/Validation.spec';
+import './functional-tests/options/input-file-name/Validation.spec';
 import './functional-tests/storages/string-array-transformers/string-array-storage/StringArrayStorage.spec';
 import './functional-tests/storages/string-array-transformers/string-array-storage/StringArrayStorage.spec';
 
 
 /**
 /**

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

@@ -307,202 +307,316 @@ describe('obfuscatedCodeFileUtils', () => {
 
 
     describe('getOutputSourceMapPath', () => {
     describe('getOutputSourceMapPath', () => {
         describe('Variant #1: output code path is a file path', () => {
         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(() => {
                 before(() => {
                     const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
@@ -511,20 +625,20 @@ describe('obfuscatedCodeFileUtils', () => {
                             output: rawOutputPath
                             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;
                 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;
                 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 rawInputPath: string = path.join('C:\\', tmpDirectoryPath, 'input', 'test-input.js');
                 const rawOutputPath: string = path.join('C:\\', tmpDirectoryPath, 'output', 'test-output.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;
                 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 rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js');
             const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.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(() => {
                     before(() => {
                         exportAllDeclarationNode = NodeFactory.exportAllDeclarationNode(
                         exportAllDeclarationNode = NodeFactory.exportAllDeclarationNode(
-                            literalNode
+                            literalNode,
+                            null
                         );
                         );
 
 
                         literalNode.parentNode = exportAllDeclarationNode;
                         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 { IInversifyContainerFacade } from '../../../src/interfaces/container/IInversifyContainerFacade';
 
 
 import { InversifyContainerFacade } from '../../../src/container/InversifyContainerFacade';
 import { InversifyContainerFacade } from '../../../src/container/InversifyContainerFacade';
+import { atob } from '../../helpers/atob';
 
 
 describe('CryptUtils', () => {
 describe('CryptUtils', () => {
     let cryptUtils: ICryptUtils;
     let cryptUtils: ICryptUtils;
@@ -21,30 +22,65 @@ describe('CryptUtils', () => {
 
 
     describe('btoa', () => {
     describe('btoa', () => {
        describe('Variant #1: basic', () => {
        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(() => {
            before(() => {
-               string = cryptUtils.btoa('string');
+               encodedString = cryptUtils.btoa('string');
+               decodedString = atob(encodedString);
            });
            });
 
 
            it('should create a base-64 encoded string from a given string', () => {
            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', () => {
         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(() => {
             before(() => {
-                string = cryptUtils.btoa('stri');
+                encodedString = cryptUtils.btoa('stri');
+                decodedString = atob(encodedString);
             });
             });
 
 
             it('should create a base-64 encoded string from a given string with padding characters', () => {
             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 = atob(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