├── .gitignore ├── CONTRIBUTING.md ├── .eslintrc.js ├── docs ├── prefer-includes.md ├── no-numeric-endings-for-variables.md ├── no-c-like-loops.md ├── no-hardcoded-password.md ├── no-filter-instead-of-find.md ├── no-then.md ├── classbody-starts-with-newline.md ├── no-duplicated-chains.md ├── no-void-map.md ├── force-native-methods.md ├── no-window.md └── no-hardcoded-configuration-data.md ├── lib └── rules │ ├── no-c-like-loops.js │ ├── no-then.js │ ├── no-numeric-endings-for-variables.js │ ├── no-void-map.js │ ├── no-filter-instead-of-find.js │ ├── force-native-methods.js │ ├── no-hardcoded-password.js │ ├── prefer-includes.js │ ├── no-window.js │ ├── classbody-starts-with-newline.js │ ├── no-hardcoded-configuration-data.js │ └── no-duplicated-chains.js ├── tests └── lib │ └── rules │ ├── no-filter-instead-of-find.js │ └── prefer-includes.js ├── package.json ├── LICENSE ├── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | *~ 4 | .idea/ 5 | *.iml 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Just find interesting issues https://github.com/WebbyLab/eslint-plugin-more/issues and choose any you want. 4 | 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | extends: [ 5 | 'eslint-config-webbylab/rules/base' 6 | ], 7 | rules: {} 8 | } 9 | -------------------------------------------------------------------------------- /docs/prefer-includes.md: -------------------------------------------------------------------------------- 1 | # Forces the use of Array.prototype.includes instead of indexOf() 2 | 3 | ## Rule Details 4 | 5 | The following pattern is considered a warning: 6 | 7 | ```js 8 | var users = [1, 2, 3, 4]; 9 | 10 | users.indexOf(2) == -1; //false 11 | ``` 12 | 13 | The following pattern is not considered a warning: 14 | 15 | ```js 16 | var users = [1, 2, 3, 4]; 17 | 18 | users.includes(2); // true 19 | 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/no-numeric-endings-for-variables.md: -------------------------------------------------------------------------------- 1 | # Prohibits the use of variables that end in numerics. 2 | 3 | Variables that end in numbers make it harder to discern their purpose. 4 | 5 | ## Rule Details 6 | 7 | The following pattern is considered a warning: 8 | 9 | ```js 10 | const user1 = 'Tony Stark'; 11 | ``` 12 | 13 | The following pattern is not considered a warning: 14 | 15 | ```js 16 | const ironMan = 'Tony Stark'; 17 | ``` 18 | -------------------------------------------------------------------------------- /lib/rules/no-c-like-loops.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = function (context) { 4 | return { 5 | ForStatement(node) { 6 | const isFor = (node.update.operator === '++') || (node.update.operator === '+='); 7 | 8 | if (isFor) { 9 | context.report({ 10 | node, 11 | message: 'Do not use c-like loop with i++ or i +=1, instead use forEach, Map, or For of' 12 | }); 13 | } 14 | } 15 | }; 16 | }; 17 | 18 | module.exports.schema = []; 19 | -------------------------------------------------------------------------------- /tests/lib/rules/no-filter-instead-of-find.js: -------------------------------------------------------------------------------- 1 | const { RuleTester } = require('eslint/lib/rule-tester') 2 | const rule = require('../../../lib/rules/no-filter-instead-of-find'); 3 | 4 | const ruleTester = new RuleTester(); 5 | 6 | ruleTester.run('no-filter-instead-of-find', rule, { 7 | valid: [ 8 | 'this.obj[0]', 9 | 'arr.filter(filterFunction)' 10 | ], 11 | invalid: [ 12 | { 13 | code: 'arr.filter(filterFunction)[0]', 14 | errors: [ { message: 'Do not use \'filter\' to find one element, use find method instead' } ] 15 | } 16 | ] 17 | }); 18 | -------------------------------------------------------------------------------- /docs/no-c-like-loops.md: -------------------------------------------------------------------------------- 1 | # Prohibits the usage of c-like loop when you iterate over array and have i++ or i +=1 2 | 3 | Error only on case when you iterate over array and have i++ or i +=1 4 | 5 | ## Rule Details 6 | 7 | Error only on case when you iterate over array and have i++ or i +=1; 8 | 9 | // Example of incrorrect code 10 | ```js 11 | for (var i = 0; i < list.length; i++) { 12 | } 13 | 14 | for (var i = 0; i < list.length; i += 1) { 15 | } 16 | ``` 17 | 18 | // Example of correct code 19 | ```js 20 | list.forEach(function(item) { 21 | 22 | }); 23 | ``` 24 | 25 | ```js 26 | for (const item of list) { 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/no-hardcoded-password.md: -------------------------------------------------------------------------------- 1 | # Prohibits using hardcoded passwords. 2 | 3 | Passwords should be passed via configuraion mechanism to provide multi-environment support. 4 | 5 | ## Rule Details 6 | 7 | The following pattern is considered a warning: 8 | 9 | ```js 10 | const password = 'DEFAULT_PASSWORD'; 11 | 12 | const variable = { 13 | password : 'DEFAULT_PASSWORD', 14 | 'password' : 'DEFAULT_PASSWORD', 15 | [`${variable}password`] : 'DEFAULT_PASSWORD' 16 | }; 17 | ``` 18 | 19 | The following pattern is not considered a warning: 20 | 21 | ```js 22 | const password = config.password; 23 | ``` -------------------------------------------------------------------------------- /docs/no-filter-instead-of-find.md: -------------------------------------------------------------------------------- 1 | # Prohibits using Array.prototype.filter to find one element in array 2 | 3 | ## Rule Details 4 | 5 | The following pattern is considered a warning: 6 | 7 | ```js 8 | const data = [{ id: 1 }, { id: 2 }, { id: 3 }]; 9 | 10 | const elements = data.filter( el => el.count === 2)[0]; 11 | 12 | ``` 13 | 14 | The following patterns are not considered warnings: 15 | 16 | ```js 17 | const data = [{ count: 10 }, { count: 20 }, { count: 30 }]; 18 | 19 | const elements = data.filter( el => el.count > 20); 20 | 21 | ``` 22 | 23 | ## When Not To Use It 24 | 25 | 1. Non-ES6 environments 26 | 2. No polyfill for ES6 Array methods present 27 | -------------------------------------------------------------------------------- /lib/rules/no-then.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = function (context) { 4 | return { 5 | CallExpression(node) { 6 | const callee = node.callee; 7 | const isThen = (callee.object && callee.object.callee && callee.object.callee.name === 'then') || 8 | (callee.property && callee.property.name === 'then'); 9 | 10 | if (!isThen) { 11 | return; 12 | } 13 | 14 | context.report({ 15 | node: callee, 16 | message: 'Do not use .then, instead use async / await' 17 | }); 18 | } 19 | }; 20 | }; 21 | 22 | module.exports.schema = []; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-more", 3 | "version": "1.0.5", 4 | "license": "MIT", 5 | "keywords": [ 6 | "eslint", 7 | "eslintplugin" 8 | ], 9 | "homepage": "https://github.com/WebbyLab/eslint-plugin-more", 10 | "description": "Extra rules for Eslint", 11 | "author": { 12 | "name": "webbylab", 13 | "url": "https://webbylab.com" 14 | }, 15 | "main": "./index.js", 16 | "repository": { 17 | "type": "git", 18 | "url": "git@github.com:WebbyLab/eslint-plugin-more.git" 19 | }, 20 | "engines": { 21 | "node": ">=6.0.0" 22 | }, 23 | "peerDependencies": { 24 | "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docs/no-then.md: -------------------------------------------------------------------------------- 1 | # Forces the use of async / await intead of then 2 | 3 | `async` / `await` can be used in conjunction with promises to make asynchronous code act in a synchronous manner. 4 | 5 | ## Rule Details 6 | 7 | The following pattern is considered a warning: 8 | 9 | ```js 10 | function getSomeData() { 11 | return fetch('http://some.url/') 12 | } 13 | 14 | getSomeData().then(response => { 15 | console.log(response); 16 | }); 17 | ``` 18 | 19 | The following pattern is not considered a warning: 20 | 21 | ```js 22 | async function getSomeData() { 23 | return fetch('http://some.url/') 24 | } 25 | 26 | const responst = await getSomeData() 27 | console.log(response); 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/classbody-starts-with-newline.md: -------------------------------------------------------------------------------- 1 | # Prohibits an empty line at the beggining of a class body 2 | 3 | For clarity, there should be a new line as the immediate line after any class definition. 4 | 5 | ## Rule Details 6 | 7 | The following pattern is considered a warning: 8 | 9 | ```js 10 | export class SomeClass { 11 | constructor() { 12 | super() 13 | } 14 | } 15 | ``` 16 | 17 | The following patterns are not considered warnings: 18 | 19 | ```js 20 | export class SomeClass { 21 | /** 22 | * This is the constructor for `SomeClass` 23 | */ 24 | constructor() { 25 | super() 26 | } 27 | } 28 | ``` 29 | 30 | ``` js 31 | export class SomeClass { 32 | 33 | constructor() { 34 | super() 35 | } 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /lib/rules/no-numeric-endings-for-variables.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = function (context) { 4 | return { 5 | VariableDeclaration(node) { 6 | const declarations = node.declarations; 7 | 8 | let isOk = true; 9 | 10 | declarations.forEach((declarationNode) => { 11 | const regexp = /\d+$/; 12 | 13 | if (regexp.test(declarationNode.id.name)) isOk = false; 14 | }); 15 | 16 | if (isOk) return; 17 | 18 | context.report({ 19 | node, 20 | message: 'Do not use variables with numeric endings' 21 | }); 22 | } 23 | }; 24 | }; 25 | 26 | module.exports.schema = []; 27 | -------------------------------------------------------------------------------- /lib/rules/no-void-map.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = function (context) { 4 | return { 5 | ExpressionStatement(node) { 6 | const whereAssignment = (node.type === 'ExpressionStatement') 7 | && (node.expression.type === 'CallExpression') 8 | && node.expression.callee 9 | && node.expression.callee.property 10 | && (node.expression.callee.property.name === 'map'); 11 | 12 | if (whereAssignment) { 13 | context.report({ 14 | node, 15 | message: 'Here you have to assign this expression to variable or add other function to map' 16 | }); 17 | } 18 | } 19 | }; 20 | }; 21 | 22 | module.exports.schema = []; 23 | -------------------------------------------------------------------------------- /docs/no-duplicated-chains.md: -------------------------------------------------------------------------------- 1 | # Prohibits the duplication of long chains like `this.props.user.name` 2 | 3 | Long chains of object calls should be assigned to a variable or destructured if used multiple times. 4 | 5 | ## Rule Details 6 | 7 | The following pattern is considered a warning: 8 | 9 | ```js 10 | render() { 11 | return (
12 |

{this.props.text}

13 |
) 14 | } 15 | ``` 16 | 17 | The following patterns are not considered warnings: 18 | 19 | ```js 20 | render() { 21 | const { className, text } = this.props; 22 | 23 | return (
24 |

{text}

25 |
) 26 | } 27 | ``` 28 | 29 | ```js 30 | render() { 31 | const props = this.props; 32 | 33 | return (
34 |

{props.text}

35 |
) 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/no-void-map.md: -------------------------------------------------------------------------------- 1 | # Forces to assign array.map to variable 2 | 3 | You have not to leave array.map without variable or property (bad example). Here you can to assign it to variable if you need new array of elements from old array (first good example ) or continue to work with new array with other property (second good example). Look carefully at examples. 4 | 5 | ## Rule Details 6 | 7 | The following pattern is considered a warning: 8 | 9 | ```js 10 | users.map(user=> user.status = "ACTIVE"); 11 | ``` 12 | 13 | The following pattern is not considered a warning: 14 | 15 | ```js 16 | var users = [{id: 1}, {id: 2}, {id: 3}]; 17 | 18 | const usersIds = users.map(user => user.id); 19 | 20 | ``` 21 | 22 | ```js 23 | var users = [{id: 1}, {id: 2}, {id: 3}]; 24 | 25 | users.map(user => user.id).forEach( id => { console.log(id) } ); 26 | 27 | ``` 28 | -------------------------------------------------------------------------------- /lib/rules/no-filter-instead-of-find.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | create(context) { 3 | return { 4 | MemberExpression(node) { 5 | const property = node.property; 6 | const isPropertyZero = property.type === 'Literal' && property.value === 0; 7 | const isFilterExpression = node.object.type === 'CallExpression' && node.object.callee 8 | && node.object.callee.property && node.object.callee.property.name === 'filter'; 9 | 10 | if (isFilterExpression && isPropertyZero) { 11 | context.report({ 12 | node, 13 | message: 'Do not use \'filter\' to find one element, use find method instead' 14 | }); 15 | } 16 | } 17 | }; 18 | } 19 | }; 20 | 21 | module.exports.schema = []; 22 | -------------------------------------------------------------------------------- /lib/rules/force-native-methods.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = function (context) { 4 | return { 5 | CallExpression(node) { 6 | const callee = node.callee; 7 | const objectName = callee.name || (callee.object && callee.object.name); 8 | const notAllowedMethods = ['find', 'findIndex', 'indexOf', 'each', 'every', 'filter', 'includes', 'map', 9 | 'reduce', 'toLower', 'toUpper', 'trim', 'keys']; 10 | 11 | if ((objectName === '_' || objectName === 'lodash' || objectName === 'underscore') 12 | && callee.property && notAllowedMethods.indexOf(callee.property.name) !== -1) { 13 | context.report({ 14 | node, 15 | message: 'Do not use lodash methods, use native instead' 16 | }); 17 | } 18 | } 19 | }; 20 | }; 21 | 22 | module.exports.schema = []; 23 | -------------------------------------------------------------------------------- /docs/force-native-methods.md: -------------------------------------------------------------------------------- 1 | # Forces the use of native methods instead of lodash/underscore 2 | 3 | Many of the array functionality present in libraries like [lodash.com](lodash) or [underscorejs.org](underscore) are available on the Array prototype. 4 | 5 | ## Rule Details 6 | 7 | The following patterns are considered warnings: 8 | 9 | ```js 10 | import _ from 'lodash'; 11 | 12 | function getEvenNumbers(numbers) { 13 | return _.filter(numbers, num => num % 2 === 0); 14 | } 15 | ``` 16 | 17 | ``` js 18 | import _ from 'lodash'; 19 | 20 | function increment(numbers) { 21 | return _.map(numbers, num => num + 1); 22 | } 23 | ``` 24 | 25 | The following patterns are not considered warnings: 26 | 27 | ```js 28 | function getEvenNumbers(numbers) { 29 | return numbers.filter(num => num % 2 === 0); 30 | } 31 | ``` 32 | 33 | ``` js 34 | function increment(numbers) { 35 | return numbers.map(num => num + 1); 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/no-window.md: -------------------------------------------------------------------------------- 1 | # Prohibits the usage of `window` global 2 | 3 | Addng variables to the global scope can cause unintended consequences. 4 | 5 | ## Rule Details 6 | 7 | The following pattern is considered a warning: 8 | 9 | ```js 10 | function setDetail(detail) { 11 | window.detail = detail; 12 | } 13 | 14 | function getDetail() { 15 | return window.detail; 16 | } 17 | ``` 18 | 19 | The following pattern is not considered a warning: 20 | 21 | ```js 22 | let __detail; 23 | 24 | function setDetail(detail) { 25 | __detail = detail; 26 | } 27 | 28 | function getDetail() { 29 | return __detail; 30 | } 31 | ``` 32 | 33 | ## Rule Options 34 | 35 | This rule can take one argument to exclude some properties calls. 36 | 37 | ``` 38 | ... 39 | "more/no-window": [, { exclude: }] 40 | ... 41 | ``` 42 | 43 | * `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. 44 | * `exclude`: optional array of methods. 45 | 46 | The default configuration is: 47 | 48 | ```js 49 | { 50 | exclude: [ 51 | 'postMessage', 52 | 'open', 53 | 'addEventListener', 54 | 'removeEventListener' 55 | ] 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 WebbyLab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/rules/no-hardcoded-password.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = context => { 3 | function inspectHardcodedPassword(node, value) { 4 | const isHardcoded = value.toLowerCase().includes('passw'); 5 | 6 | if (isHardcoded) { 7 | return context.report({ 8 | node, 9 | message : 'Do not use hardcoded password' 10 | }); 11 | } 12 | } 13 | 14 | return { 15 | VariableDeclarator({ id, init }) { 16 | if (id && id.name && init && init.type === 'Literal' ) { 17 | return inspectHardcodedPassword(id, id.name); 18 | } 19 | }, 20 | 21 | Property({ key, value }) { 22 | if (!key || !value || value.type !== 'Literal') return; 23 | 24 | if (key.type === 'Identifier') { 25 | return inspectHardcodedPassword(key, key.name); 26 | } 27 | 28 | if (key.type === 'Literal') { 29 | return inspectHardcodedPassword(key, key.value); 30 | } 31 | 32 | if (key.type === 'TemplateLiteral') { 33 | return key.quasis.some(element => inspectHardcodedPassword(key, element.value.raw)); 34 | } 35 | } 36 | }; 37 | }; 38 | 39 | module.exports.schema = []; -------------------------------------------------------------------------------- /lib/rules/prefer-includes.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = function (context) { 4 | return { 5 | BinaryExpression(node) { 6 | const left = node.left || {}; 7 | const right = node.right || {}; 8 | const operator = node.operator; 9 | 10 | const isIndexOfCall = left.callee 11 | && (left.callee.type === 'MemberExpression') 12 | && (left.callee.property.name === 'indexOf'); 13 | 14 | if (!isIndexOfCall) { 15 | return; 16 | } 17 | 18 | const compareWithMinusOne = right.operator === '-' && right.argument && right.argument.value === 1; 19 | const lessThanZero = operator === '<' && right.value === 0; 20 | const moreOrEqualThanZero = operator === '>=' && right.value === 0; 21 | 22 | const isIndexOfEqualToMinusOne = compareWithMinusOne 23 | || lessThanZero 24 | || moreOrEqualThanZero; 25 | 26 | if (isIndexOfEqualToMinusOne) { 27 | context.report({ 28 | node, 29 | message: 'Do not use indexOf, instead use includes' 30 | }); 31 | } 32 | } 33 | }; 34 | }; 35 | 36 | module.exports.schema = []; 37 | -------------------------------------------------------------------------------- /tests/lib/rules/prefer-includes.js: -------------------------------------------------------------------------------- 1 | const { RuleTester } = require('eslint/lib/rule-tester') 2 | const rule = require('../../../lib/rules/prefer-includes'); 3 | 4 | const ruleTester = new RuleTester(); 5 | 6 | ruleTester.run('prefer-includes', rule, { 7 | valid: [ 8 | 'arr.indexOf(2) === 2', 9 | 'arr.indexOf(2) === 0', 10 | 'indexOf(2) >= 0', 11 | 'indexOf(2) < 0', 12 | 'indexOf(2) === -1', 13 | '[1, 2, 3, 4].includes(2)', 14 | // Expressions below used to cause ESLint parser bugs 15 | 'foo() + "bar"', 16 | 'obj.field += sum - some.value', 17 | 'parseInt(obj.field, 10) > 5 ? \'abc\' : obj.field' 18 | ], 19 | invalid: [ 20 | { 21 | code: 'arr.indexOf(2) >= 0', 22 | errors: [ { message: 'Do not use indexOf, instead use includes' } ] 23 | }, 24 | { 25 | code: 'arr.indexOf(2) === -1', 26 | errors: [ { message: 'Do not use indexOf, instead use includes' } ] 27 | }, 28 | { 29 | code: 'arr.indexOf(2) < 0', 30 | errors: [ { message: 'Do not use indexOf, instead use includes' } ] 31 | }, 32 | { 33 | code: '[1, 2, 3, 4].indexOf(2) === -1', 34 | errors: [ { message: 'Do not use indexOf, instead use includes' } ] 35 | } 36 | ] 37 | }); 38 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | rules: { 5 | 'no-void-map': require('./lib/rules/no-void-map.js'), 6 | 'no-c-like-loops': require('./lib/rules/no-c-like-loops.js'), 7 | 'prefer-includes': require('./lib/rules/prefer-includes.js'), 8 | 'no-then': require('./lib/rules/no-then'), 9 | 'no-window': require('./lib/rules/no-window'), 10 | 'no-numeric-endings-for-variables': require('./lib/rules/no-numeric-endings-for-variables'), 11 | 'force-native-methods': require('./lib/rules/force-native-methods'), 12 | 'no-duplicated-chains': require('./lib/rules/no-duplicated-chains'), 13 | 'classbody-starts-with-newline': require('./lib/rules/classbody-starts-with-newline'), 14 | 'no-filter-instead-of-find': require('./lib/rules/no-filter-instead-of-find'), 15 | 'no-hardcoded-password': require('./lib/rules/no-hardcoded-password'), 16 | 'no-hardcoded-configuration-data': require('./lib/rules/no-hardcoded-configuration-data') 17 | }, 18 | configs: { 19 | recommended: { 20 | rules: { 21 | 'more/no-void-map': 2, 22 | 'more/no-c-like-loops': 2, 23 | 'more/prefer-includes': 2, 24 | 'more/no-then': 2, 25 | 'more/no-window': 2, 26 | 'more/no-numeric-endings-for-variables': 2, 27 | 'more/force-native-methods': 2, 28 | 'more/no-duplicated-chains': 2, 29 | 'more/classbody-starts-with-newline': [2, 'never'], 30 | 'more/no-filter-instead-of-find': 2, 31 | 'more/no-hardcoded-password': 2, 32 | 'more/no-hardcoded-configuration-data': 2 33 | } 34 | } 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /docs/no-hardcoded-configuration-data.md: -------------------------------------------------------------------------------- 1 | # Prohibits using hardcoded configuration data. 2 | 3 | Configuration data should be passed via configuraion mechanism to provide multi-environment support. 4 | 5 | ## Rule Details 6 | 7 | The following pattern is considered a warning: 8 | 9 | ```js 10 | const ip = '192.168.1.1'; 11 | const uuid = 'b0d4ce5d-2757-4699-948c-cfa72ba94f86'; 12 | const token = 'AEYGF7K0DM1X'; 13 | const domain = 'domain.com' 14 | 15 | const object = { 16 | ip : '192.168.1.1', 17 | uuid : 'b0d4ce5d-2757-4699-948c-cfa72ba94f86', 18 | token : 'AEYGF7K0DM1X', 19 | domain : 'domain.com' 20 | }; 21 | ``` 22 | 23 | The following pattern is not considered a warning: 24 | 25 | ```js 26 | const ip = config.ip; 27 | const uuid = config.uuid; 28 | const token = config.token; 29 | const domain = config.domain 30 | 31 | const object = { 32 | ip: config.ip, 33 | uuid: config.uuid, 34 | token: config.token, 35 | domain: config.domain 36 | }; 37 | ``` 38 | 39 | ## Rule Options 40 | 41 | This rule can take arguments to forbid and exclude some configuration data. 42 | 43 | ``` 44 | "more/no-hardcoded-configuration-data": [ 45 | , 46 | { 47 | forbidContaining : , 48 | excludeContaining : 49 | } 50 | ] 51 | ``` 52 | 53 | * `enabled` : for enabling the rule. 0=off, 1=warn, 2=error. 54 | * `forbidContaining` : optional array of stings for forbid. 55 | * `excludeContaining` : optional array of stings for exlude. 56 | 57 | The default configuration is: 58 | 59 | ``` 60 | { 61 | forbidContaining : ["ipAddress", "UUID", "alphanumericToken", "domainName"], 62 | excludeContaining : ['facebook', 'google', 'yandex'] 63 | } 64 | ``` -------------------------------------------------------------------------------- /lib/rules/no-window.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (context) { 3 | const DEFAULT = ['postMessage', 'open', 'addEventListener', 'removeEventListener']; 4 | const exclude = context.options[0] && context.options[0].exclude || DEFAULT; 5 | 6 | function report(node) { 7 | context.report({ 8 | node, 9 | message: 'Avoid using window' 10 | }); 11 | } 12 | 13 | function isWindow(name) { 14 | return name === 'window'; 15 | } 16 | 17 | function checkExclude(name) { 18 | return exclude.indexOf(name) === -1; 19 | } 20 | 21 | return { 22 | MemberExpression(node) { 23 | if (!node.object) return; 24 | 25 | const objectName = node.object.name || (node.object.expression && node.object.expression.name); 26 | 27 | if (!isWindow(objectName)) return; 28 | 29 | const propName = node.property.name; 30 | 31 | if (checkExclude(propName)) { 32 | report(node); 33 | } 34 | }, 35 | 36 | VariableDeclarator(node) { 37 | if (!node.init) return; 38 | 39 | const initName = node.init.name; 40 | 41 | if (!isWindow(initName)) return; 42 | 43 | for (const property of node.id.properties) { 44 | const propName = property.key.name; 45 | 46 | if (checkExclude(propName)) { 47 | report(node.init); 48 | } 49 | } 50 | } 51 | 52 | }; 53 | }; 54 | 55 | module.exports.schema = [ { 56 | type: 'object', 57 | properties: { 58 | exclude: { 59 | type: 'array', 60 | items: { 61 | type: 'string' 62 | } 63 | } 64 | }, 65 | additionalProperties: false 66 | } ]; 67 | -------------------------------------------------------------------------------- /lib/rules/classbody-starts-with-newline.js: -------------------------------------------------------------------------------- 1 | // No empty string after class definition (before varructor) (with fixer) 2 | 3 | module.exports = function (context) { 4 | const sourceCode = context.getSourceCode(); 5 | 6 | const mode = context.options[0] || 'never'; 7 | 8 | function checkForNewLine(node) { 9 | const nextNode = node.body[0]; 10 | 11 | if (!nextNode) { 12 | return; 13 | } 14 | 15 | if (nextNode.type !== 'ClassProperty' && nextNode.type !== 'MethodDefinition') { 16 | return; 17 | } 18 | 19 | const nextLineNum = node.loc.start.line + 1; 20 | let bodyStartsWithNewLine = nextNode.loc.start.line > nextLineNum; 21 | 22 | 23 | const comments = sourceCode.getComments(nextNode).leading; 24 | 25 | if (comments.length && comments[0].loc.start.line === nextLineNum) { 26 | // It is not a new line it is a comment 27 | bodyStartsWithNewLine = false; 28 | } 29 | 30 | 31 | if (mode === 'never' && bodyStartsWithNewLine) { 32 | context.report({ 33 | node, 34 | fix: fixer => { 35 | return fixer.replaceTextRange([node.start + 1, nextNode.start - nextNode.loc.start.column], '\n'); 36 | }, 37 | message: 'Do not start class body with a newline' 38 | }); 39 | } else if (mode === 'always' && !bodyStartsWithNewLine) { 40 | context.report({ 41 | node, 42 | loc: { 43 | start: { line: nextLineNum }, 44 | end: { line: nextLineNum } 45 | }, 46 | fix: fixer => fixer.insertTextAfterRange([0, node.start + 1], '\n'), 47 | message: 'Start class body with a newline' 48 | }); 49 | } 50 | } 51 | 52 | return { 53 | ClassBody: checkForNewLine 54 | }; 55 | }; 56 | 57 | module.exports.schema = [ 58 | { 59 | enum: ['never', 'always'] 60 | } 61 | ]; 62 | -------------------------------------------------------------------------------- /lib/rules/no-hardcoded-configuration-data.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = context => { 3 | const REGEX = { 4 | ipAddress : /((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/i, 5 | UUID : /[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}$/i, 6 | alphanumericToken : /(?=.*[a-z])(?=.*[0-9])(?=[a-z0-9]{12,})/i, 7 | domainName : /(?!:\/\/)([a-zA-Z0-9-_]+\.)*[a-zA-Z0-9][a-zA-Z0-9-_]+\.[a-zA-Z]{2,11}?/ 8 | }; 9 | 10 | const DEFAULT_FORBID = [ 'ipAddress', 'UUID', 'alphanumericToken', 'domainName' ]; 11 | const DEFAULT_EXCLUDE = [ 'facebook', 'google', 'yandex' ]; 12 | 13 | const forbid = context.options[0] && context.options[0].forbidContaining || DEFAULT_FORBID; 14 | const exclude = context.options[0] && context.options[0].excludeContaining || DEFAULT_EXCLUDE; 15 | 16 | function inspect(regex, node, value) { 17 | if (typeof value !== 'string') return; 18 | 19 | if (exclude.some(name => value.toLowerCase().includes(name.toLowerCase()))) return; 20 | 21 | if (REGEX[regex].test(value)) { 22 | return context.report({ 23 | node, 24 | message : 'Do not use hardcoded configuration data' 25 | }); 26 | } 27 | } 28 | 29 | return { 30 | VariableDeclarator({ init }) { 31 | if (init && init.type === 'Literal') { 32 | return forbid.some(rule => inspect(rule, init, init.value)); 33 | } 34 | }, 35 | 36 | Property({ value }) { 37 | if (value && value.type === 'Literal') { 38 | return forbid.some(rule => inspect(rule, value, value.value)); 39 | } 40 | } 41 | }; 42 | }; 43 | 44 | module.exports.schema = [ { 45 | type : 'object', 46 | properties : { 47 | forbidContaining : { 48 | type : 'array', 49 | items : { 50 | type : 'string' 51 | } 52 | }, 53 | excludeContaining : { 54 | type : 'array', 55 | items : { 56 | type : 'string' 57 | } 58 | } 59 | }, 60 | additionalProperties : false 61 | } ]; 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eslint-plugin-more - extra rules for Eslint 2 | 3 | [![NPM](https://nodei.co/npm/eslint-plugin-more.png?compact=true)](https://npmjs.org/package/eslint-plugin-more) 4 | 5 | Eslint is a very great tool and it already has tons of rules. Eslint allows us to reduce amount of time required for code review. But, of course, eslint cannot cover all the issues. So, we do in the following way, if during code review we see, that we comment on thing that is possible to check automatically, then we create new eslint rule. 6 | 7 | Some of this rules will go to upstream after proving them in our codebase. 8 | 9 | # Installation 10 | 11 | Install [ESLint](https://www.github.com/eslint/eslint) either locally or globally. 12 | 13 | ```sh 14 | $ npm install eslint 15 | ``` 16 | 17 | If you installed `ESLint` globally, you have to install this plugin globally too. Otherwise, install it locally. 18 | 19 | ```sh 20 | $ npm install eslint-plugin-more 21 | ``` 22 | 23 | # Configuration 24 | 25 | Add `plugins` section and specify ESLint-plugin-more as a plugin. 26 | 27 | ```json 28 | { 29 | "plugins": [ 30 | "more" 31 | ] 32 | } 33 | ``` 34 | 35 | Finally, enable all of the rules that you would like to use. For example: 36 | 37 | ```json 38 | "rules": { 39 | "more/no-void-map": 2, 40 | "more/no-c-like-loops": 2, 41 | "more/prefer-includes": 2, 42 | "more/no-then": 2, 43 | "more/no-window": 2, 44 | "more/no-numeric-endings-for-variables": 2, 45 | "more/no-filter-instead-of-find": 2, 46 | "more/force-native-methods": 2, 47 | "more/no-duplicated-chains": 2, 48 | "more/classbody-starts-with-newline": [2, 'never'], 49 | "more/no-hardcoded-password": 2, 50 | "more/no-hardcoded-configuration-data": 2 51 | } 52 | ``` 53 | 54 | # Supported rules 55 | * [no-void-map](docs/no-void-map.md): Prohibits the use of array.map without variable or property 56 | * [no-c-like-loops](docs/no-c-like-loops.md): Prohibits the use of 'For loop' with ++ or += 57 | * [prefer-includes](docs/prefer-includes.md): Prohibits the use of comparison array.indexOf() == -1 and ask to use 'includes' instead 58 | * [no-then](docs/no-then.md): Forces the use of async / await instead of then 59 | * [no-window](docs/no-window.md): Prohibits the usage of `window` global 60 | * [force-native-methods](docs/force-native-methods.md): - Forces the use of native methods instead of lodash/underscore 61 | * [no-filter-instead-of-find](docs/no-filter-instead-of-find.md): - Prohibits using Array.prototype.filter to find one element and asks to use 'find' instead. 62 | * [no-numeric-endings-for-variables](docs/no-numeric-endings-for-variables.md): - Prohibits the use of variables that end in numerics. 63 | * [no-duplicated-chains](docs/no-duplicated-chains.md): - Prohibits the duplication of long chains like `this.props.user.name` 64 | * [classbody-starts-with-newline](docs/classbody-starts-with-newline.md) - Prohibits an empty line at the beggining of a class body 65 | * [no-hardcoded-password](docs/no-hardcoded-password.md) - Prohibits using hardcoded passwords. 66 | * [no-hardcoded-configuration-data](docs/no-hardcoded-configuration-data.md) - Prohibits using hardcoded configuration data. 67 | 68 | ## Author 69 | WebbyLab (https://webbylab.com) 70 | -------------------------------------------------------------------------------- /lib/rules/no-duplicated-chains.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = function (context) { 4 | const longChains = []; 5 | let isInsideCallExpression = false; 6 | 7 | // to avoid subexpressions output 8 | let memberExpressionsDepth = 0; 9 | 10 | function startFunction() { 11 | longChains.push({}); 12 | } 13 | 14 | function endFunction() { 15 | longChains.pop(); 16 | } 17 | 18 | function incrementChainCount(chain) { 19 | if (longChains.length) { 20 | if (longChains[longChains.length - 1][chain]) { 21 | longChains[longChains.length - 1][chain]++; 22 | } else { 23 | longChains[longChains.length - 1][chain] = 1; 24 | } 25 | 26 | return longChains[longChains.length - 1][chain]; 27 | } 28 | 29 | return 0; 30 | } 31 | 32 | function pauseChecking() { 33 | isInsideCallExpression = true; 34 | } 35 | 36 | function resumeChecking() { 37 | isInsideCallExpression = false; 38 | } 39 | 40 | function checkLongChainForDuplication(node) { 41 | memberExpressionsDepth++; 42 | 43 | if (memberExpressionsDepth > 1 44 | || isInsideCallExpression 45 | || !node.object 46 | || node.object.type !== 'MemberExpression' 47 | || node.computed 48 | ) { 49 | return; 50 | } 51 | 52 | 53 | const pathParts = []; 54 | let parent = node; 55 | let depth = 0; 56 | 57 | while (parent) { 58 | if (parent.computed) { 59 | break; 60 | } 61 | 62 | if (parent.type === 'ThisExpression') { 63 | pathParts.push('this'); 64 | } else if (parent.type === 'MemberExpression') { 65 | pathParts.push(parent.property.name); 66 | } else if (parent.type === 'Identifier') { 67 | pathParts.push(parent.name); 68 | } else { 69 | break; 70 | } 71 | 72 | depth++; 73 | parent = parent.object; 74 | } 75 | 76 | 77 | if (depth <= 2) { 78 | return; 79 | } 80 | 81 | const path = pathParts.reverse().join('.'); 82 | const chainCount = incrementChainCount(path); 83 | 84 | if (chainCount >= 2) { 85 | context.report({ 86 | node, 87 | message: `Do not duplicate long chains. Assign "${ path }" to a variable or destruct it.` 88 | }); 89 | } 90 | } 91 | 92 | return { 93 | 'FunctionDeclaration': startFunction, 94 | 'FunctionExpression': startFunction, 95 | 'ArrowFunctionExpression': startFunction, 96 | 'FunctionDeclaration:exit': endFunction, 97 | 'FunctionExpression:exit': endFunction, 98 | 'ArrowFunctionExpression:exit': endFunction, 99 | 'MemberExpression': checkLongChainForDuplication, 100 | 'MemberExpression:exit'() { 101 | memberExpressionsDepth--; 102 | }, 103 | 'CallExpression': pauseChecking, 104 | 'CallExpression:exit': resumeChecking 105 | 106 | }; 107 | }; 108 | 109 | module.exports.schema = []; 110 | --------------------------------------------------------------------------------