Repository: webpack-contrib/uglifyjs-webpack-plugin (js)

Issue:
Couldn't parse bundle asset "vendors~typeahead.chunk.min.js". Analyzer will use module sizes from stats file.
Plugin version 1.2.4
webpack 4

This only happens in production when setting ` sourceMap: false`, when set to true, it runs fine!

```
 new UglifyJsPlugin({ 
            sourceMap: false, // <-- using false here, generates the error above
            uglifyOptions: {
                ecma: 5,
                warnings: true
            }
        })
```

**Here the complete Log-file:**
[2018-03-16T16_31_25_817Z-debug.log](https://github.com/webpack-contrib/uglifyjs-webpack-plugin/files/1820016/2018-03-16T16_31_25_817Z-debug.log)



Pull request description:
fix: don't handle invalid source map
fixes #261

Also refactor tests

Patch:
diff --git a/README.md b/README.md
index 46ca064..32ab2ea 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
 	<p>This plugin uses <a href="https://github.com/mishoo/UglifyJS2/tree/harmony">UglifyJS v3 </a><a href="https://npmjs.com/package/uglify-es">(`uglify-es`)</a> to minify your JavaScript</p>
 </div>
 
-> ℹ️  `webpack =< v3.0.0` currently contains [`v0.4.6`](https://github.com/webpack-contrib/uglifyjs-webpack-plugin/tree/version-0.4) of this plugin under `webpack.optimize.UglifyJsPlugin` as an alias. For usage of the latest version (`v1.0.0`), please follow the instructions below. Aliasing `v1.0.0` as `webpack.optimize.UglifyJsPlugin` is scheduled for `webpack v4.0.0`
+> ℹ️  `webpack < v4.0.0` currently contains [`v0.4.6`](https://github.com/webpack-contrib/uglifyjs-webpack-plugin/tree/version-0.4) of this plugin under `webpack.optimize.UglifyJsPlugin` as an alias. For usage of the latest version (`v1.0.0`), please follow the instructions below. Aliasing `v1.0.0` as `webpack.optimize.UglifyJsPlugin` is scheduled for `webpack v4.0.0`
 
 <h2 align="center">Install</h2>
 
diff --git a/src/index.js b/src/index.js
index 145a83c..ecef4d0 100644
--- a/src/index.js
+++ b/src/index.js
@@ -12,6 +12,7 @@ import serialize from 'serialize-javascript';
 import schema from './options.json';
 import Uglify from './uglify';
 import versions from './uglify/versions';
+import utils from './utils';
 
 const warningRegex = /\[.+:([0-9]+),([0-9]+)\]/;
 
@@ -122,9 +123,18 @@ class UglifyJsPlugin {
               const { source, map } = asset.sourceAndMap();
 
               input = source;
-              inputSourceMap = map;
 
-              sourceMap = new SourceMapConsumer(inputSourceMap);
+              if (utils.isSourceMap(map)) {
+                inputSourceMap = map;
+                sourceMap = new SourceMapConsumer(inputSourceMap);
+              } else {
+                inputSourceMap = map;
+                sourceMap = null;
+
+                compilation.warnings.push(
+                  new Error(`${file} contain invalid source map`),
+                );
+              }
             } else {
               input = asset.source();
               inputSourceMap = null;
diff --git a/src/utils/index.js b/src/utils/index.js
new file mode 100644
index 0000000..37b63c9
--- /dev/null
+++ b/src/utils/index.js
@@ -0,0 +1,13 @@
+function isSourceMap(input) {
+  // All required options for `new SourceMapConsumer(...options)`
+  // https://github.com/mozilla/source-map#new-sourcemapconsumerrawsourcemap
+  return Boolean(input &&
+    input.version &&
+    input.sources &&
+    input.names &&
+    input.mappings);
+}
+
+export default {
+  isSourceMap,
+};


Test patch:
diff --git a/test/__snapshots__/source-map-options.test.js.snap b/test/__snapshots__/source-map-options.test.js.snap
index d6d27cf..b750e4b 100644
--- a/test/__snapshots__/source-map-options.test.js.snap
+++ b/test/__snapshots__/source-map-options.test.js.snap
@@ -1,5 +1,13 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`when options.sourceMap true and options.parallel true compilation handler when called optimize-chunk-assets handler only calls callback once: errors 1`] = `Array []`;
+
+exports[`when options.sourceMap true and options.parallel true compilation handler when called optimize-chunk-assets handler only calls callback once: warnings 1`] = `
+Array [
+  [Error: test4.js contain invalid source map],
+]
+`;
+
 exports[`when options.sourceMap true and options.parallel true matches snapshot: asset main.0c220ec66316af2c1b24.js 1`] = `"webpackJsonp([0],[function(o,n){o.exports=function(){console.log(7)}}],[0]);"`;
 
 exports[`when options.sourceMap true and options.parallel true matches snapshot: asset manifest.d6857f782c13a99b5917.js 1`] = `"!function(r){var n=window.webpackJsonp;window.webpackJsonp=function(e,u,c){for(var f,i,p,a=0,l=[];a<e.length;a++)i=e[a],o[i]&&l.push(o[i][0]),o[i]=0;for(f in u)Object.prototype.hasOwnProperty.call(u,f)&&(r[f]=u[f]);for(n&&n(e,u,c);l.length;)l.shift()();if(c)for(a=0;a<c.length;a++)p=t(t.s=c[a]);return p};var e={},o={1:0};function t(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return r[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=r,t.c=e,t.d=function(r,n,e){t.o(r,n)||Object.defineProperty(r,n,{configurable:!1,enumerable:!0,get:e})},t.n=function(r){var n=r&&r.__esModule?function(){return r.default}:function(){return r};return t.d(n,\\"a\\",n),n},t.o=function(r,n){return Object.prototype.hasOwnProperty.call(r,n)},t.p=\\"\\",t.oe=function(r){throw console.error(r),r}}([]);"`;
@@ -192,6 +200,14 @@ Object {
 
 exports[`when options.sourceMap true and options.parallel true matches snapshot: warnings 1`] = `Array []`;
 
+exports[`when options.sourceMap true compilation handler when called optimize-chunk-assets handler only calls callback once: errors 1`] = `Array []`;
+
+exports[`when options.sourceMap true compilation handler when called optimize-chunk-assets handler only calls callback once: warnings 1`] = `
+Array [
+  [Error: test4.js contain invalid source map],
+]
+`;
+
 exports[`when options.sourceMap true matches snapshot: asset main.0c220ec66316af2c1b24.js 1`] = `"webpackJsonp([0],[function(o,n){o.exports=function(){console.log(7)}}],[0]);"`;
 
 exports[`when options.sourceMap true matches snapshot: asset manifest.d6857f782c13a99b5917.js 1`] = `"!function(r){var n=window.webpackJsonp;window.webpackJsonp=function(e,u,c){for(var f,i,p,a=0,l=[];a<e.length;a++)i=e[a],o[i]&&l.push(o[i][0]),o[i]=0;for(f in u)Object.prototype.hasOwnProperty.call(u,f)&&(r[f]=u[f]);for(n&&n(e,u,c);l.length;)l.shift()();if(c)for(a=0;a<c.length;a++)p=t(t.s=c[a]);return p};var e={},o={1:0};function t(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return r[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=r,t.c=e,t.d=function(r,n,e){t.o(r,n)||Object.defineProperty(r,n,{configurable:!1,enumerable:!0,get:e})},t.n=function(r){var n=r&&r.__esModule?function(){return r.default}:function(){return r};return t.d(n,\\"a\\",n),n},t.o=function(r,n){return Object.prototype.hasOwnProperty.call(r,n)},t.p=\\"\\",t.oe=function(r){throw console.error(r),r}}([]);"`;
diff --git a/test/parallel-options.test.js b/test/parallel-options.test.js
index 67673c3..e64a7fc 100644
--- a/test/parallel-options.test.js
+++ b/test/parallel-options.test.js
@@ -78,7 +78,7 @@ describe('when options.parallel', () => {
         beforeEach(() => {
           chunkPluginEnvironment = new PluginEnvironment();
           compilation = chunkPluginEnvironment.getEnvironmentStub();
-          compilation.assets = assets;
+          compilation.assets = Object.assign({}, assets);
           compilation.errors = [];
 
           workerFarm.mockClear();
@@ -183,7 +183,8 @@ describe('when options.parallel', () => {
         beforeEach(() => {
           chunkPluginEnvironment = new PluginEnvironment();
           compilation = chunkPluginEnvironment.getEnvironmentStub();
-          compilation.assets = assets;
+          compilation.assets = Object.assign({}, assets);
+          compilation.warnings = [];
           compilation.errors = [];
 
           workerFarm.mockClear();
@@ -290,7 +291,8 @@ describe('when options.parallel', () => {
         beforeEach(() => {
           chunkPluginEnvironment = new PluginEnvironment();
           compilation = chunkPluginEnvironment.getEnvironmentStub();
-          compilation.assets = assets;
+          compilation.assets = Object.assign({}, assets);
+          compilation.warnings = [];
           compilation.errors = [];
 
           workerFarm.mockClear();
diff --git a/test/source-map-options.test.js b/test/source-map-options.test.js
index 2342898..1c220f0 100644
--- a/test/source-map-options.test.js
+++ b/test/source-map-options.test.js
@@ -20,6 +20,14 @@ describe('when options.sourceMap', () => {
     'test3.js': {
       source: () => 'function test3(foo) { foo = 1; }',
     },
+    'test4.js': {
+      sourceAndMap: () => {
+        return {
+          source: 'function foo(x) { if (x) { return bar(); not_called1(); } }',
+          map: null,
+        };
+      },
+    },
   };
 
   describe('true', () => {
@@ -62,7 +70,8 @@ describe('when options.sourceMap', () => {
         beforeEach(() => {
           chunkPluginEnvironment = new PluginEnvironment();
           compilation = chunkPluginEnvironment.getEnvironmentStub();
-          compilation.assets = assets;
+          compilation.assets = Object.assign({}, assets);
+          compilation.warnings = [];
           compilation.errors = [];
 
           eventBinding.handler(compilation);
@@ -107,8 +116,10 @@ describe('when options.sourceMap', () => {
           it('only calls callback once', (done) => {
             callback = jest.fn();
             compilationEventBinding.handler([{
-              files: ['test.js', 'test1.js', 'test2.js', 'test3.js'],
+              files: ['test.js', 'test1.js', 'test2.js', 'test3.js', 'test4.js'],
             }], () => {
+              expect(compilation.warnings).toMatchSnapshot('warnings');
+              expect(compilation.errors).toMatchSnapshot('errors');
               callback();
               expect(callback.mock.calls.length).toBe(1);
               done();
@@ -183,7 +194,8 @@ describe('when options.sourceMap', () => {
         beforeEach(() => {
           chunkPluginEnvironment = new PluginEnvironment();
           compilation = chunkPluginEnvironment.getEnvironmentStub();
-          compilation.assets = assets;
+          compilation.assets = Object.assign({}, assets);
+          compilation.warnings = [];
           compilation.errors = [];
 
           eventBinding.handler(compilation);
@@ -228,8 +240,10 @@ describe('when options.sourceMap', () => {
           it('only calls callback once', (done) => {
             callback = jest.fn();
             compilationEventBinding.handler([{
-              files: ['test.js', 'test1.js', 'test2.js', 'test3.js'],
+              files: ['test.js', 'test1.js', 'test2.js', 'test3.js', 'test4.js'],
             }], () => {
+              expect(compilation.warnings).toMatchSnapshot('warnings');
+              expect(compilation.errors).toMatchSnapshot('errors');
               callback();
               expect(callback.mock.calls.length).toBe(1);
               done();
diff --git a/test/utils/index.test.js b/test/utils/index.test.js
new file mode 100644
index 0000000..3899b08
--- /dev/null
+++ b/test/utils/index.test.js
@@ -0,0 +1,21 @@
+import utils from '../../src/utils';
+
+describe('utils', () => {
+  it('isSourceMap', () => {
+    const rawSourceMap = {
+      version: 3,
+      file: 'min.js',
+      names: ['bar', 'baz', 'n'],
+      sources: ['one.js', 'two.js'],
+      sourceRoot: 'http://example.com/www/js/',
+      mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA',
+    };
+
+    expect(utils.isSourceMap(null)).toBe(false);
+    expect(utils.isSourceMap()).toBe(false);
+    expect(utils.isSourceMap({})).toBe(false);
+    expect(utils.isSourceMap([])).toBe(false);
+    expect(utils.isSourceMap('foo')).toBe(false);
+    expect(utils.isSourceMap(rawSourceMap)).toBe(true);
+  });
+});