diff --git a/.eslintrc.yml b/.eslintrc.yml index e8e97312..7b4a3787 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -4,6 +4,8 @@ env: es6: true node: true mocha: true +plugins: + - lodash extends: 'eslint:recommended' rules: @@ -571,3 +573,75 @@ rules: - error - before: true after: false + + # Lodash + + lodash/chain-style: + - error + - explicit + lodash/identity-shorthand: + - error + - always + lodash/import-scope: + - error + - full + lodash/matches-prop-shorthand: + - error + - always + lodash/matches-shorthand: + - error + - always + lodash/no-commit: + - error + lodash/path-style: + - error + - array + lodash/prefer-compact: + - error + lodash/prefer-filter: + - error + - 5 + lodash/prefer-flat-map: + - error + lodash/prefer-invoke-map: + - error + lodash/prefer-map: + - error + lodash/prefer-reject: + - error + lodash/prefer-thru: + - error + lodash/prefer-wrapper-method: + - error + lodash/prop-shorthand: + - error + - always + lodash/prefer-constant: + - error + - true + - true + lodash/prefer-get: + - error + - 2 + lodash/prefer-includes: + - error + - includeNative: true + lodash/prefer-is-nil: + - error + lodash/prefer-lodash-chain: + - error + lodash/prefer-lodash-method: + - error + lodash/prefer-lodash-typecheck: + - error + lodash/prefer-matches: + - error + - 3 + lodash/prefer-noop: + - error + lodash/prefer-over-quantifier: + - error + lodash/prefer-startswith: + - error + lodash/prefer-times: + - error diff --git a/lib/child-writer/writer-proxy.js b/lib/child-writer/writer-proxy.js index 426c2e09..25bdb657 100644 --- a/lib/child-writer/writer-proxy.js +++ b/lib/child-writer/writer-proxy.js @@ -127,9 +127,10 @@ return isElevated().then((elevated) => { // Translate the current arguments to point to the AppImage // Relative paths are resolved from `/tmp/.mount_XXXXXX/usr` - const translatedArguments = _.map(_.tail(process.argv), (argv) => { - return argv.replace(path.join(process.env.APPDIR, 'usr/'), ''); - }); + const translatedArguments = _.chain(process.argv) + .tail() + .invokeMap('replace', path.join(process.env.APPDIR, 'usr/'), '') + .value(); return commandPrefix .concat([ process.env.APPIMAGE ]) diff --git a/lib/cli/errors.js b/lib/cli/errors.js index c336281f..6aa76801 100644 --- a/lib/cli/errors.js +++ b/lib/cli/errors.js @@ -43,18 +43,14 @@ exports.HUMAN_FRIENDLY = { * @memberof HUMAN_FRIENDLY * @returns {String} message */ - EPERM: () => { - return 'You\'re not authorized to perform this operation'; - }, + EPERM: _.constant('You\'re not authorized to perform this operation'), /** * @property {Function} EACCES * @memberof HUMAN_FRIENDLY * @returns {String} message */ - EACCES: () => { - return 'You\'re don\'t have access to this resource'; - } + EACCES: _.constant('You don\'t have access to this resource') /* eslint-enable new-cap */ diff --git a/lib/gui/models/flash-state.js b/lib/gui/models/flash-state.js index 5ecd2bca..74c66447 100644 --- a/lib/gui/models/flash-state.js +++ b/lib/gui/models/flash-state.js @@ -189,7 +189,7 @@ FlashState.service('FlashStateModel', function() { * } */ this.wasLastFlashCancelled = () => { - return _.get(this.getFlashResults(), 'cancelled', false); + return _.get(this.getFlashResults(), [ 'cancelled' ], false); }; /** diff --git a/lib/gui/models/selection-state.js b/lib/gui/models/selection-state.js index e1fdea9d..583aad86 100644 --- a/lib/gui/models/selection-state.js +++ b/lib/gui/models/selection-state.js @@ -111,7 +111,7 @@ SelectionStateModel.service('SelectionStateModel', function(DrivesModel) { * const image = SelectionStateModel.getImage(); */ this.getImage = () => { - return _.get(Store.getState().toJS(), 'selection.image'); + return _.get(Store.getState().toJS(), [ 'selection', 'image' ]); }; /** @@ -125,7 +125,11 @@ SelectionStateModel.service('SelectionStateModel', function(DrivesModel) { * const imagePath = SelectionStateModel.getImagePath(); */ this.getImagePath = () => { - return _.get(Store.getState().toJS(), 'selection.image.path'); + return _.get(Store.getState().toJS(), [ + 'selection', + 'image', + 'path' + ]); }; /** @@ -139,7 +143,11 @@ SelectionStateModel.service('SelectionStateModel', function(DrivesModel) { * const imageSize = SelectionStateModel.getImageSize(); */ this.getImageSize = () => { - return _.get(Store.getState().toJS(), 'selection.image.size'); + return _.get(Store.getState().toJS(), [ + 'selection', + 'image', + 'size' + ]); }; /** @@ -153,7 +161,11 @@ SelectionStateModel.service('SelectionStateModel', function(DrivesModel) { * const imageUrl = SelectionStateModel.getImageUrl(); */ this.getImageUrl = () => { - return _.get(Store.getState().toJS(), 'selection.image.url'); + return _.get(Store.getState().toJS(), [ + 'selection', + 'image', + 'url' + ]); }; /** @@ -167,7 +179,11 @@ SelectionStateModel.service('SelectionStateModel', function(DrivesModel) { * const imageName = SelectionStateModel.getImageName(); */ this.getImageName = () => { - return _.get(Store.getState().toJS(), 'selection.image.name'); + return _.get(Store.getState().toJS(), [ + 'selection', + 'image', + 'name' + ]); }; /** @@ -181,7 +197,11 @@ SelectionStateModel.service('SelectionStateModel', function(DrivesModel) { * const imageLogo = SelectionStateModel.getImageLogo(); */ this.getImageLogo = () => { - return _.get(Store.getState().toJS(), 'selection.image.logo'); + return _.get(Store.getState().toJS(), [ + 'selection', + 'image', + 'logo' + ]); }; /** @@ -195,7 +215,11 @@ SelectionStateModel.service('SelectionStateModel', function(DrivesModel) { * const imageSupportUrl = SelectionStateModel.getImageSupportUrl(); */ this.getImageSupportUrl = () => { - return _.get(Store.getState().toJS(), 'selection.image.supportUrl'); + return _.get(Store.getState().toJS(), [ + 'selection', + 'image', + 'supportUrl' + ]); }; /** @@ -209,7 +233,11 @@ SelectionStateModel.service('SelectionStateModel', function(DrivesModel) { * const imageRecommendedDriveSize = SelectionStateModel.getImageRecommendedDriveSize(); */ this.getImageRecommendedDriveSize = () => { - return _.get(Store.getState().toJS(), 'selection.image.recommendedDriveSize'); + return _.get(Store.getState().toJS(), [ + 'selection', + 'image', + 'recommendedDriveSize' + ]); }; /** @@ -316,7 +344,7 @@ SelectionStateModel.service('SelectionStateModel', function(DrivesModel) { return false; } - return drive === _.get(this.getDrive(), 'device'); + return drive === _.get(this.getDrive(), [ 'device' ]); }; }); diff --git a/lib/gui/models/store.js b/lib/gui/models/store.js index 72c69168..bffaf680 100644 --- a/lib/gui/models/store.js +++ b/lib/gui/models/store.js @@ -75,6 +75,30 @@ const ACTIONS = _.fromPairs(_.map([ return [ message, message ]; })); +/** + * @summary Find a drive from the list of available drives + * @function + * @private + * + * @param {Object} state - application state + * @param {String} device - drive device + * @returns {(Object|Undefined)} drive + * + * @example + * const drive = findDrive(state, '/dev/disk2'); + */ +const findDrive = (state, device) => { + + /* eslint-disable lodash/prefer-lodash-method */ + + return state.get('availableDrives').find((drive) => { + return drive.get('device') === device; + }); + + /* eslint-enable lodash/prefer-lodash-method */ + +}; + /** * @summary The redux store reducer * @function @@ -156,7 +180,7 @@ const storeReducer = (state = DEFAULT_STATE, action) => { throw new Error(`Invalid state type: ${action.data.type}`); } - if (_.isUndefined(action.data.percentage) || _.isNull(action.data.percentage)) { + if (_.isNil(action.data.percentage)) { throw new Error('Missing state percentage'); } @@ -172,7 +196,7 @@ const storeReducer = (state = DEFAULT_STATE, action) => { throw new Error(`Invalid state eta: ${action.data.eta}`); } - if (_.isUndefined(action.data.speed) || _.isNull(action.data.speed)) { + if (_.isNil(action.data.speed)) { throw new Error('Missing state speed'); } @@ -231,9 +255,7 @@ const storeReducer = (state = DEFAULT_STATE, action) => { throw new Error(`Invalid drive: ${action.data}`); } - const selectedDrive = state.get('availableDrives').find((drive) => { - return drive.get('device') === action.data; - }); + const selectedDrive = findDrive(state, action.data); if (!selectedDrive) { throw new Error(`The drive is not available: ${action.data}`); @@ -280,10 +302,7 @@ const storeReducer = (state = DEFAULT_STATE, action) => { throw new Error(`Invalid image logo: ${action.data.logo}`); } - const selectedDevice = state.getIn([ 'selection', 'drive' ]); - const selectedDrive = state.get('availableDrives').find((drive) => { - return drive.get('device') === selectedDevice; - }); + const selectedDrive = findDrive(state, state.getIn([ 'selection', 'drive' ])); return _.attempt(() => { if (selectedDrive && !_.every([ diff --git a/lib/gui/models/supported-formats.js b/lib/gui/models/supported-formats.js index 062355e5..eb92b0c6 100644 --- a/lib/gui/models/supported-formats.js +++ b/lib/gui/models/supported-formats.js @@ -42,8 +42,8 @@ SupportedFormats.service('SupportedFormatsModel', function() { */ const getExtensionsFromTypeGetter = (type) => { return () => { - return _.map(_.filter(imageStream.supportedFileTypes, (fileType) => { - return fileType.type === type; + return _.map(_.filter(imageStream.supportedFileTypes, { + type }), 'extension'); }; }; diff --git a/lib/gui/modules/analytics.js b/lib/gui/modules/analytics.js index 5dc28f95..5727000c 100644 --- a/lib/gui/modules/analytics.js +++ b/lib/gui/modules/analytics.js @@ -159,7 +159,7 @@ analytics.service('AnalyticsService', function($log, $window, $mixpanel, Setting * } */ this.shouldReportError = (error) => { - return !_.has(error, 'report') || Boolean(error.report); + return !_.has(error, [ 'report' ]) || Boolean(error.report); }; /** diff --git a/lib/gui/modules/drive-scanner.js b/lib/gui/modules/drive-scanner.js index 386e542f..6cd2e0d4 100644 --- a/lib/gui/modules/drive-scanner.js +++ b/lib/gui/modules/drive-scanner.js @@ -37,10 +37,15 @@ driveScanner.factory('DriveScannerService', (SettingsModel) => { const DRIVE_SCANNER_FIRST_SCAN_DELAY_MS = 0; const emitter = new EventEmitter(); + /* eslint-disable lodash/prefer-lodash-method */ + const availableDrives = Rx.Observable.timer( DRIVE_SCANNER_FIRST_SCAN_DELAY_MS, DRIVE_SCANNER_INTERVAL_MS ) + + /* eslint-enable lodash/prefer-lodash-method */ + .flatMap(() => { return Rx.Observable.fromNodeCallback(drivelist.list)(); }) @@ -63,8 +68,8 @@ driveScanner.factory('DriveScannerService', (SettingsModel) => { return drives; } - return _.reject(drives, (drive) => { - return drive.system; + return _.reject(drives, { + system: true }); }) .pausable(new Rx.Subject()); diff --git a/lib/gui/utils/byte-size/byte-size.js b/lib/gui/utils/byte-size/byte-size.js index 81b8e8e7..f85153fa 100644 --- a/lib/gui/utils/byte-size/byte-size.js +++ b/lib/gui/utils/byte-size/byte-size.js @@ -26,6 +26,11 @@ const angular = require('angular'); const MODULE_NAME = 'Etcher.Utils.ByteSize'; const ByteSize = angular.module(MODULE_NAME, []); + +/* eslint-disable lodash/prefer-lodash-method */ + ByteSize.filter('gigabyte', require('./filters/gigabyte')); +/* eslint-enable lodash/prefer-lodash-method */ + module.exports = MODULE_NAME; diff --git a/lib/gui/utils/path/path.js b/lib/gui/utils/path/path.js index a24a022a..aa55d9fe 100644 --- a/lib/gui/utils/path/path.js +++ b/lib/gui/utils/path/path.js @@ -26,6 +26,11 @@ const angular = require('angular'); const MODULE_NAME = 'Etcher.Utils.Path'; const Path = angular.module(MODULE_NAME, []); + +/* eslint-disable lodash/prefer-lodash-method */ + Path.filter('basename', require('./filters/basename')); +/* eslint-enable lodash/prefer-lodash-method */ + module.exports = MODULE_NAME; diff --git a/lib/image-stream/archive-hooks/zip.js b/lib/image-stream/archive-hooks/zip.js index 205a38cd..9c357a61 100644 --- a/lib/image-stream/archive-hooks/zip.js +++ b/lib/image-stream/archive-hooks/zip.js @@ -51,8 +51,8 @@ exports.getEntries = (archive) => { const EMPTY_ENTRY_SIZE = 0; return resolve(_.chain(zip.entries()) - .omitBy((entry) => { - return entry.size === EMPTY_ENTRY_SIZE; + .omitBy({ + size: EMPTY_ENTRY_SIZE }) .map((metadata) => { return { diff --git a/lib/image-stream/utils.js b/lib/image-stream/utils.js index afd58dbb..91c6be52 100644 --- a/lib/image-stream/utils.js +++ b/lib/image-stream/utils.js @@ -39,5 +39,5 @@ exports.getArchiveMimeType = (file) => { const MAGIC_NUMBER_BUFFER_END = 261; const chunk = readChunk.sync(file, MAGIC_NUMBER_BUFFER_START, MAGIC_NUMBER_BUFFER_END); - return _.get(archiveType(chunk), 'mime', 'application/octet-stream'); + return _.get(archiveType(chunk), [ 'mime' ], 'application/octet-stream'); }; diff --git a/lib/shared/drive-constraints.js b/lib/shared/drive-constraints.js index 47c4ed1a..1e5e0647 100644 --- a/lib/shared/drive-constraints.js +++ b/lib/shared/drive-constraints.js @@ -49,7 +49,7 @@ const UNKNOWN_SIZE = 0; * } */ exports.isDriveLocked = (drive) => { - return Boolean(_.get(drive, 'protected', false)); + return Boolean(_.get(drive, [ 'protected' ], false)); }; /** @@ -71,7 +71,7 @@ exports.isDriveLocked = (drive) => { * } */ exports.isSystemDrive = (drive) => { - return Boolean(_.get(drive, 'system', false)); + return Boolean(_.get(drive, [ 'system' ], false)); }; /** @@ -108,8 +108,8 @@ exports.isSystemDrive = (drive) => { * } */ exports.isSourceDrive = (drive, image) => { - const mountpoints = _.get(drive, 'mountpoints', []); - const imagePath = _.get(image, 'path'); + const mountpoints = _.get(drive, [ 'mountpoints' ], []); + const imagePath = _.get(image, [ 'path' ]); if (!imagePath || _.isEmpty(mountpoints)) { return false; @@ -142,7 +142,7 @@ exports.isSourceDrive = (drive, image) => { * } */ exports.isDriveLargeEnough = (drive, image) => { - return _.get(drive, 'size', UNKNOWN_SIZE) >= _.get(image, 'size', UNKNOWN_SIZE); + return _.get(drive, [ 'size' ], UNKNOWN_SIZE) >= _.get(image, [ 'size' ], UNKNOWN_SIZE); }; /** @@ -206,5 +206,5 @@ exports.isDriveValid = (drive, image) => { * } */ exports.isDriveSizeRecommended = (drive, image) => { - return _.get(drive, 'size', UNKNOWN_SIZE) >= _.get(image, 'recommendedDriveSize', UNKNOWN_SIZE); + return _.get(drive, [ 'size' ], UNKNOWN_SIZE) >= _.get(image, [ 'recommendedDriveSize' ], UNKNOWN_SIZE); }; diff --git a/lib/shared/robot/index.js b/lib/shared/robot/index.js index 71434050..ba0008d5 100644 --- a/lib/shared/robot/index.js +++ b/lib/shared/robot/index.js @@ -32,7 +32,7 @@ const _ = require('lodash'); * } */ exports.isEnabled = (environment) => { - const value = _.get(environment, 'ETCHER_CLI_ROBOT', false); + const value = _.get(environment, [ 'ETCHER_CLI_ROBOT' ], false); return Boolean(value === 'false' ? false : value); }; @@ -177,7 +177,7 @@ exports.recomposeErrorMessage = (message) => { * > 'foo' */ exports.getCommand = (message) => { - return _.get(message, 'command'); + return _.get(message, [ 'command' ]); }; /** @@ -200,7 +200,7 @@ exports.getCommand = (message) => { * > { foo: 1 } */ exports.getData = (message) => { - return _.get(message, 'data', {}); + return _.get(message, [ 'data' ], {}); }; /** diff --git a/package.json b/package.json index fb770b33..b1e4fc91 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "electron-packager": "^7.0.1", "electron-prebuilt": "1.4.4", "eslint": "^3.16.1", + "eslint-plugin-lodash": "^2.3.5", "file-exists": "^1.0.0", "html-angular-validate": "^0.1.9", "jsonfile": "^2.3.1", diff --git a/tests/cli/errors.spec.js b/tests/cli/errors.spec.js index c7d76a12..16dcd687 100644 --- a/tests/cli/errors.spec.js +++ b/tests/cli/errors.spec.js @@ -90,7 +90,7 @@ describe('CLI: Errors', function() { it('should provide a friendly message for EACCES', function() { const error = new Error('foo bar'); error.code = 'EACCES'; - const message = 'EACCES: You\'re don\'t have access to this resource'; + const message = 'EACCES: You don\'t have access to this resource'; m.chai.expect(errors.getErrorMessage(error)).to.equal(message); }); diff --git a/versionist.conf.js b/versionist.conf.js index 6e30d4d2..b3e27d7b 100644 --- a/versionist.conf.js +++ b/versionist.conf.js @@ -16,6 +16,8 @@ 'use strict'; +const _ = require('lodash'); + module.exports = { subjectParser: 'angular', @@ -40,16 +42,20 @@ module.exports = { }, transformTemplateData: (data) => { - data.features = data.commits.filter((commit) => { - return commit.subject.type === 'feat'; + data.features = _.filter(data.commits, { + subject: { + type: 'feat' + } }); - data.fixes = data.commits.filter((commit) => { - return commit.subject.type === 'fix'; + data.fixes = _.filter(data.commits, { + subject: { + type: 'fix' + } }); - data.misc = data.commits.filter((commit) => { - return ![ 'fix', 'feat' ].includes(commit.subject.type); + data.misc = _.filter(data.commits, (commit) => { + return !_.includes([ 'fix', 'feat' ], commit.subject.type); }); return data;