ESLint improvements (#204)

* Add proper ESLint configuration

* Move test-build script for easier linting

* Fix ESLint violations instead of ignoring them

* Remove unused function

* Remove node-only code

* Check that we only use ES5 in our browser code
diff --git a/test/build.js b/build-tools/test-build.js
similarity index 92%
rename from test/build.js
rename to build-tools/test-build.js
index 63947d0..90b23fd 100755
--- a/test/build.js
+++ b/build-tools/test-build.js
@@ -2,7 +2,7 @@
 
 const fs = require('fs-extra');
 const path = require('path');
-const { build, collectModules } = require('../build-tools');
+const { build, collectModules } = require('.');
 
 // Istanbul is provided by karma-coverage
 const { Instrumenter } = require('istanbul');
@@ -51,6 +51,6 @@
     });
 
     // Finally, add modules provided by test platform
-    const testModulesPath = path.join(__dirname, 'test-platform-modules');
+    const testModulesPath = path.join(__dirname, '../test/test-platform-modules');
     return Object.assign(...platformModules, collectModules(testModulesPath));
 }
diff --git a/package.json b/package.json
index 9dd7aa1..91c31ce 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,7 @@
     "pretest": "npm run build:test",
     "test": "npm run eslint && karma start",
     "build": "grunt compile",
-    "build:test": "node test/build.js pkg/cordova.test.js"
+    "build:test": "node build-tools/test-build pkg/cordova.test.js"
   },
   "license": "Apache-2.0",
   "contributors": [
@@ -69,6 +69,7 @@
     }
   ],
   "dependencies": {
+    "eslint-plugin-es5": "^1.4.1",
     "execa": "^1.0.0",
     "fs-extra": "^8.0.1",
     "globby": "^9.2.0"
diff --git a/src/.eslintrc.yml b/src/.eslintrc.yml
new file mode 100644
index 0000000..11dfeda
--- /dev/null
+++ b/src/.eslintrc.yml
@@ -0,0 +1,12 @@
+extends:
+    - plugin:es5/no-es2015
+    - plugin:es5/no-es2016
+
+env:
+    node: false
+    commonjs: true
+    browser: true
+
+globals:
+    define: false
+    PLATFORM_VERSION_BUILD_LABEL: false
diff --git a/src/common/base64.js b/src/common/base64.js
index e15dc4c..5c0b3f4 100644
--- a/src/common/base64.js
+++ b/src/common/base64.js
@@ -27,7 +27,7 @@
 };
 
 base64.toArrayBuffer = function (str) {
-    var decodedStr = typeof atob !== 'undefined' ? atob(str) : Buffer.from(str, 'base64').toString('binary'); // eslint-disable-line no-undef
+    var decodedStr = atob(str);
     var arrayBuffer = new ArrayBuffer(decodedStr.length);
     var array = new Uint8Array(arrayBuffer);
     for (var i = 0, len = decodedStr.length; i < len; i++) {
diff --git a/src/common/channel.js b/src/common/channel.js
index 9182968..03c76c9 100644
--- a/src/common/channel.js
+++ b/src/common/channel.js
@@ -93,14 +93,14 @@
         }
         if (!len) h();
     },
-    /* eslint-disable no-return-assign */
+
     create: function (type) {
-        return channel[type] = new Channel(type, false);
+        return (channel[type] = new Channel(type, false));
     },
     createSticky: function (type) {
-        return channel[type] = new Channel(type, true);
+        return (channel[type] = new Channel(type, true));
     },
-    /* eslint-enable no-return-assign */
+
     /**
      * cordova Channels that must fire before "deviceready" is fired.
      */
@@ -221,7 +221,6 @@
  * Calls all functions subscribed to this channel.
  */
 Channel.prototype.fire = function (e) {
-    var fail = false; // eslint-disable-line no-unused-vars
     var fireArgs = Array.prototype.slice.call(arguments);
     // Apply stickiness.
     if (this.state === 1) {
diff --git a/src/common/modulemapper.js b/src/common/modulemapper.js
index a280d16..d815a99 100644
--- a/src/common/modulemapper.js
+++ b/src/common/modulemapper.js
@@ -19,7 +19,7 @@
 */
 
 var builder = require('cordova/builder');
-var moduleMap = define.moduleMap; // eslint-disable-line no-undef
+var moduleMap = define.moduleMap;
 var symbolList;
 var deprecationMap;
 
@@ -59,12 +59,9 @@
     if (!symbolPath) {
         return context;
     }
-    var parts = symbolPath.split('.');
-    var cur = context;
-    for (var i = 0, part; part = parts[i]; ++i) { // eslint-disable-line no-cond-assign
-        cur = cur[part] = cur[part] || {};
-    }
-    return cur;
+    return symbolPath.split('.').reduce(function (cur, part) {
+        return (cur[part] = cur[part] || {});
+    }, context);
 }
 
 exports.mapModules = function (context) {
diff --git a/src/common/pluginloader.js b/src/common/pluginloader.js
index c8c4fa3..3cc55a6 100644
--- a/src/common/pluginloader.js
+++ b/src/common/pluginloader.js
@@ -35,11 +35,11 @@
 
 function injectIfNecessary (id, url, onload, onerror) {
     onerror = onerror || onload;
-    if (id in define.moduleMap) { // eslint-disable-line no-undef
+    if (id in define.moduleMap) {
         onload();
     } else {
         exports.injectScript(url, function () {
-            if (id in define.moduleMap) { // eslint-disable-line no-undef
+            if (id in define.moduleMap) {
                 onload();
             } else {
                 onerror();
@@ -50,7 +50,7 @@
 
 function onScriptLoadingComplete (moduleList, finishPluginLoading) {
     // Loop through all the plugins and then through their clobbers and merges.
-    for (var i = 0, module; module = moduleList[i]; i++) { // eslint-disable-line no-cond-assign
+    for (var i = 0, module; (module = moduleList[i]); i++) {
         if (module.clobbers && module.clobbers.length) {
             for (var j = 0; j < module.clobbers.length; j++) {
                 modulemapper.clobbers(module.id, module.clobbers[j]);
diff --git a/src/common/utils.js b/src/common/utils.js
index febfd91..d0edc7f 100644
--- a/src/common/utils.js
+++ b/src/common/utils.js
@@ -108,10 +108,11 @@
 
     retVal = {};
     for (i in obj) {
-        // https://issues.apache.org/jira/browse/CB-11522 'unknown' type may be returned in
-        // custom protocol activation case on Windows Phone 8.1 causing "No such interface supported" exception
-        // on cloning.
-        if ((!(i in retVal) || retVal[i] !== obj[i]) && typeof obj[i] !== 'undefined' && typeof obj[i] !== 'unknown') { // eslint-disable-line valid-typeof
+        // 'unknown' type may be returned in custom protocol activation case on
+        // Windows Phone 8.1 causing "No such interface supported" exception on
+        // cloning (https://issues.apache.org/jira/browse/CB-11522)
+        // eslint-disable-next-line valid-typeof
+        if ((!(i in retVal) || retVal[i] !== obj[i]) && typeof obj[i] !== 'undefined' && typeof obj[i] !== 'unknown') {
             retVal[i] = utils.clone(obj[i]);
         }
     }
diff --git a/src/cordova.js b/src/cordova.js
index 838ae95..7bb09ea 100644
--- a/src/cordova.js
+++ b/src/cordova.js
@@ -21,7 +21,7 @@
 
 // Workaround for Windows 10 in hosted environment case
 // http://www.w3.org/html/wg/drafts/html/master/browsers.html#named-access-on-the-window-object
-if (window.cordova && !(window.cordova instanceof HTMLElement)) { // eslint-disable-line no-undef
+if (window.cordova && !(window.cordova instanceof HTMLElement)) {
     throw new Error('cordova already defined');
 }
 
@@ -94,7 +94,6 @@
     return event;
 }
 
-/* eslint-disable no-undef */
 var cordova = {
     define: define,
     require: require,
@@ -102,8 +101,6 @@
     platformVersion: PLATFORM_VERSION_BUILD_LABEL,
     platformId: platform.id,
 
-    /* eslint-enable no-undef */
-
     /**
      * Methods to add/remove your own addEventListener hijacking on document + window.
      */
diff --git a/test/.eslintrc.yml b/test/.eslintrc.yml
index 45b3991..bcbaf1e 100644
--- a/test/.eslintrc.yml
+++ b/test/.eslintrc.yml
@@ -1,4 +1,6 @@
 env:
+    node: false
+    browser: true
     jasmine: true
 
 globals:
diff --git a/test/android/test.exec.js b/test/android/test.exec.js
index cb025ee..30b3873 100644
--- a/test/android/test.exec.js
+++ b/test/android/test.exec.js
@@ -35,12 +35,11 @@
         // Avoid a log message warning about the lack of _nativeApi.
         exec.setJsToNativeBridgeMode(exec.jsToNativeModes.PROMPT);
         nativeApiProvider.set(nativeApi);
-        /* eslint-disable no-undef */
-        var origPrompt = typeof prompt === 'undefined' ? undefined : prompt;
-        prompt = function () { return 1234; };
+
+        var origPrompt = window.prompt;
+        window.prompt = function () { return 1234; };
         exec.init();
-        prompt = origPrompt;
-        /* eslint-enable no-undef */
+        window.prompt = origPrompt;
     });
 
     afterEach(function () {
diff --git a/test/ios/test.exec.js b/test/ios/test.exec.js
index e310c21..9acbfed 100644
--- a/test/ios/test.exec.js
+++ b/test/ios/test.exec.js
@@ -36,19 +36,6 @@
         });
     });
 
-    function simulateNativeBehaviour (codes) { // eslint-disable-line no-unused-vars
-        var execPayload = JSON.parse(exec.nativeFetchMessages());
-        while (execPayload.length && codes.length) {
-            var curPayload = execPayload.shift();
-            var callbackId = curPayload[0];
-            var moreResults = exec.nativeCallback(callbackId, codes.shift(), 'payload', false);
-            if (moreResults) {
-                execPayload.push.apply(execPayload, JSON.parse(moreResults));
-            }
-        }
-        expect(codes.length).toBe(0, 'Wrong number of results.');
-    }
-
     describe('exec', function () {
         it('Test#001 : should return "" from nativeFetchMessages work when nothing is pending.', function () {
             var execPayload = exec.nativeFetchMessages();
diff --git a/test/test-platform-modules/.eslintrc.yml b/test/test-platform-modules/.eslintrc.yml
new file mode 100644
index 0000000..e3d49d8
--- /dev/null
+++ b/test/test-platform-modules/.eslintrc.yml
@@ -0,0 +1,4 @@
+env:
+    node: false
+    commonjs: true
+    browser: true
diff --git a/test/test-platform-modules/exec.js b/test/test-platform-modules/exec.js
index 566d681..4812348 100644
--- a/test/test-platform-modules/exec.js
+++ b/test/test-platform-modules/exec.js
@@ -19,4 +19,5 @@
  *
 */
 
-module.exports = jasmine.createSpy(); // eslint-disable-line no-undef
+/* eslint-env jasmine */
+module.exports = jasmine.createSpy();
diff --git a/test/test.base64.js b/test/test.base64.js
index 41b92ad..a326a5e 100644
--- a/test/test.base64.js
+++ b/test/test.base64.js
@@ -39,19 +39,23 @@
     it('Test#002 : can base64 encode a binary string in an ArrayBuffer', function () {
         var arrayBuffer = new ArrayBuffer(256);
         var view = new Uint8Array(arrayBuffer);
-        /* eslint-disable no-undef */
-        base64string = 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==';
-
         for (var i = 0; i < view.length; i++) {
             view[i] = i;
         }
 
-        expect(base64.fromArrayBuffer(arrayBuffer)).toBe(base64string);
+        expect(base64.fromArrayBuffer(arrayBuffer)).toBe(
+            'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v' +
+            'MDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f' +
+            'YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6P' +
+            'kJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/' +
+            'wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v' +
+            '8PHy8/T19vf4+fr7/P3+/w=='
+        );
     });
 
     it('Test#003 : can base64 encode an text string in an ArrayBuffer', function () {
         var orig = 'Some Awesome Test This Is!';
-        var base64string = typeof btoa !== 'undefined' ? btoa(orig) : Buffer.from('Some Awesome Test This Is!', 'binary').toString('base64');
+        var base64string = btoa(orig);
         var arrayBuffer = new ArrayBuffer(orig.length);
         var view = new Uint8Array(arrayBuffer);
 
@@ -64,8 +68,8 @@
 
     it('Test#004 : can decode a base64-encoded text string into an ArrayBuffer', function () {
         var orig = 'Some Awesome Test This Is!';
-        var base64string = typeof btoa !== 'undefined' ? btoa(orig) : Buffer.from(orig, 'binary').toString('base64');
-        /* eslint-enable no-undef */
+        var base64string = btoa(orig);
+
         var arrayBuffer = base64.toArrayBuffer(base64string);
 
         var testString = '';
diff --git a/test/test.pluginloader.js b/test/test.pluginloader.js
index 5b569f6..ed5c9f9 100644
--- a/test/test.pluginloader.js
+++ b/test/test.pluginloader.js
@@ -45,11 +45,11 @@
         injectScript.and.callFake(function (url, onload, onerror) {
             // jsdom deficiencies:
             if (typeof location !== 'undefined') {
-                expect(url).toBe(window.location.href.replace(/\/[^\/]*?$/, '/foo/cordova_plugins.js')); // eslint-disable-line no-useless-escape
+                expect(url).toBe(window.location.href.replace(/\/[^/]*?$/, '/foo/cordova_plugins.js'));
             } else {
                 expect(url).toBe('foo/cordova_plugins.js');
             }
-            /* eslint-disable no-undef */
+
             define('cordova/plugin_list', function (require, exports, module) {
                 module.exports = [];
             });
@@ -79,7 +79,7 @@
         injectScript.and.callFake(function (url, onload, onerror) {
             // jsdom deficiencies:
             if (typeof location !== 'undefined') {
-                expect(url).toBe(window.location.href.replace(/\/[^\/]*?$/, '/foo/some/path.js')); // eslint-disable-line no-useless-escape
+                expect(url).toBe(window.location.href.replace(/\/[^/]*?$/, '/foo/some/path.js'));
             } else {
                 expect(url).toBe('foo/some/path.js');
             }
diff --git a/test/test.urlutil.js b/test/test.urlutil.js
index 6dae288..e5749a9 100644
--- a/test/test.urlutil.js
+++ b/test/test.urlutil.js
@@ -43,7 +43,7 @@
     });
 
     it('Test#003 : can handle relative URLs', function () {
-        var rootUrl = window.location.href.replace(/[?#].*/, '').replace(/[^\/]*$/, ''); // eslint-disable-line no-useless-escape
+        var rootUrl = window.location.href.replace(/[?#].*/, '').replace(/[^/]*$/, '');
         expect(urlutil.makeAbsolute('foo?a#b')).toBe(rootUrl + 'foo?a#b');
         expect(urlutil.makeAbsolute('foo/b%20ar')).toBe(rootUrl + 'foo/b%20ar');
     });
diff --git a/test/test.utils.js b/test/test.utils.js
index c2ea462..bde0cb4 100644
--- a/test/test.utils.js
+++ b/test/test.utils.js
@@ -61,7 +61,8 @@
             expect(isArray).toBe(true);
         });
         it('Test#009 : should return true for new Array().', function () {
-            var isArray = utils.isArray(new Array()); // eslint-disable-line no-array-constructor
+            // eslint-disable-next-line no-array-constructor
+            var isArray = utils.isArray(new Array());
             expect(isArray).toBe(true);
         });
         it('Test#010 : should return false for {}.', function () {