-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #35 from Expensify/marcaaron-custom-rules
Add Custom Rules
- Loading branch information
Showing
33 changed files
with
6,671 additions
and
874 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
node_modules | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
module.exports = { | ||
MESSAGE: { | ||
NO_API_IN_VIEWS: 'Do not call API directly outside of actions methods. Only actions should make API requests.', | ||
NO_INLINE_NAMED_EXPORT: 'Do not inline named exports.', | ||
NO_NEGATED_VARIABLES: 'Do not use negated variable names.', | ||
NO_THENABLE_ACTIONS_IN_VIEWS: 'Calling .then() on action method {{method}} is forbidden in React views. Relocate this logic into the actions file and pass values via Onyx.', | ||
NO_USELESS_COMPOSE: 'compose() is not necessary when passed a single argument', | ||
PREFER_ACTIONS_SET_DATA: 'Only actions should directly set or modify Onyx data. Please move this logic into a suitable action.', | ||
PREFER_EARLY_RETURN: 'Prefer an early return to a conditionally-wrapped function body', | ||
PREFER_IMPORT_MODULE_CONTENTS: 'Do not import individual exports from local modules. Prefer \'import * as\' syntax.', | ||
PREFER_ONYX_CONNECT_IN_LIBS: 'Only call Onyx.connect() from inside a /src/libs/** file. React components and non-library code should not use Onyx.connect()', | ||
PREFER_UNDERSCORE_METHOD: 'Prefer \'_.{{method}}\' over the native function.', | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
const {isInActionFile, isInTestFile} = require('./utils'); | ||
const message = require('./CONST').MESSAGE.NO_API_IN_VIEWS; | ||
|
||
module.exports = { | ||
create: context => ({ | ||
Identifier(node) { | ||
if (isInActionFile(context.getFilename())) { | ||
return; | ||
} | ||
|
||
if (isInTestFile(context.getFilename())) { | ||
return; | ||
} | ||
|
||
if (node.name !== 'API') { | ||
return; | ||
} | ||
|
||
context.report({ | ||
node, | ||
message, | ||
}); | ||
}, | ||
}), | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
const message = require('./CONST').MESSAGE.NO_INLINE_NAMED_EXPORT; | ||
|
||
module.exports = { | ||
create: context => ({ | ||
ExportNamedDeclaration(node) { | ||
if (!node.declaration) { | ||
return; | ||
} | ||
|
||
context.report({ | ||
node, | ||
message, | ||
}); | ||
}, | ||
}), | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
const _ = require('underscore'); | ||
const lodashGet = require('lodash/get'); | ||
const message = require('./CONST').MESSAGE.NO_NEGATED_VARIABLES; | ||
|
||
/** | ||
* @param {String} string | ||
* @returns {Boolean} | ||
*/ | ||
function isFalsePositive(string) { | ||
return _.some(['notification', 'notch'], falsePositive => string.toLowerCase().includes(falsePositive)); | ||
} | ||
|
||
/** | ||
* @param {String} name | ||
* @returns {Boolean} | ||
*/ | ||
function isNegatedVariableName(name) { | ||
if (!name) { | ||
return; | ||
} | ||
|
||
return (name.includes('Not') && !isFalsePositive(name)) | ||
|| name.includes('isNot' && !isFalsePositive(name)) | ||
|| name.includes('cannot') | ||
|| name.includes('shouldNot') | ||
|| name.includes('cant') | ||
|| name.includes('dont'); | ||
} | ||
|
||
module.exports = { | ||
create: context => ({ | ||
FunctionDeclaration(node) { | ||
const name = lodashGet(node, 'id.name'); | ||
if (!name) { | ||
return; | ||
} | ||
|
||
if (!isNegatedVariableName(name)) { | ||
return; | ||
} | ||
|
||
context.report({ | ||
node, | ||
message, | ||
}); | ||
}, | ||
VariableDeclarator(node) { | ||
const name = lodashGet(node, 'id.name'); | ||
if (!name) { | ||
return; | ||
} | ||
|
||
if (!isNegatedVariableName(node.id.name)) { | ||
return; | ||
} | ||
|
||
context.report({ | ||
node, | ||
message, | ||
}); | ||
}, | ||
}), | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
const _ = require('underscore'); | ||
const lodashGet = require('lodash/get'); | ||
const path = require('path'); | ||
const isReactViewFile = require('./utils').isReactViewFile; | ||
const message = require('./CONST').MESSAGE.NO_THENABLE_ACTIONS_IN_VIEWS; | ||
|
||
module.exports = { | ||
create: (context) => { | ||
const actionsNamespaces = []; | ||
return { | ||
// Using import declaration to create a map of all the imports for this file and which ones are "actions" | ||
ImportDeclaration(node) { | ||
const pathName = path.resolve(lodashGet(node, 'source.value')); | ||
if (!pathName || !pathName.includes('/actions/')) { | ||
return; | ||
} | ||
|
||
actionsNamespaces.push(_.last(pathName.split('/'))); | ||
}, | ||
MemberExpression(node) { | ||
if (!isReactViewFile(context.getFilename())) { | ||
return; | ||
} | ||
|
||
if (lodashGet(node, 'property.name') !== 'then') { | ||
return; | ||
} | ||
|
||
const actionModuleName = lodashGet(node, 'object.callee.object.name'); | ||
if (!_.includes(actionsNamespaces, actionModuleName)) { | ||
return; | ||
} | ||
|
||
const actionMethodName = lodashGet(node, 'object.callee.property.name'); | ||
context.report({ | ||
node, | ||
message, | ||
data: { | ||
method: `${actionModuleName}.${actionMethodName}()`, | ||
}, | ||
}); | ||
}, | ||
}; | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
const lodashGet = require('lodash/get'); | ||
const message = require('./CONST').MESSAGE.NO_USELESS_COMPOSE; | ||
|
||
module.exports = { | ||
create: context => ({ | ||
CallExpression(node) { | ||
const name = lodashGet(node, 'callee.name'); | ||
if (!name) { | ||
return; | ||
} | ||
|
||
if (name !== 'compose') { | ||
return; | ||
} | ||
|
||
if (node.arguments.length !== 1) { | ||
return; | ||
} | ||
|
||
context.report({ | ||
node, | ||
message, | ||
}); | ||
}, | ||
}), | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
const _ = require('underscore'); | ||
const lodashGet = require('lodash/get'); | ||
const {isOnyxMethodCall, isInActionFile, isInTestFile} = require('./utils'); | ||
const message = require('./CONST').MESSAGE.PREFER_ACTIONS_SET_DATA; | ||
|
||
/** | ||
* @param {String} methodName | ||
* @returns {Boolean} | ||
*/ | ||
function isDataSettingMethod(methodName) { | ||
return _.includes(['set', 'merge', 'mergeCollection'], methodName); | ||
} | ||
|
||
module.exports = { | ||
create: context => ({ | ||
MemberExpression(node) { | ||
const filename = context.getFilename(); | ||
|
||
if (!isOnyxMethodCall(node)) { | ||
return; | ||
} | ||
|
||
if (isInTestFile(context.getFilename())) { | ||
return; | ||
} | ||
|
||
const methodName = lodashGet(node, 'property.name'); | ||
if (!isDataSettingMethod(methodName) || isInActionFile(context.getFilename(filename))) { | ||
return; | ||
} | ||
|
||
context.report({ | ||
node, | ||
message, | ||
}); | ||
}, | ||
}), | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/** | ||
* Adapted from https://github.com/Shopify/web-configs/blob/84c180fb08968276198faade21fa6918b104804c/packages/eslint-plugin/lib/rules/prefer-early-return.js#L1-L78 | ||
*/ | ||
const defaultMaximumStatements = 0; | ||
const message = require('./CONST').MESSAGE.PREFER_EARLY_RETURN; | ||
|
||
module.exports = { | ||
create(context) { | ||
const options = context.options[0] || { | ||
maximumStatements: defaultMaximumStatements, | ||
}; | ||
const maxStatements = options.maximumStatements; | ||
|
||
function isLonelyIfStatement(statement) { | ||
return statement.type === 'IfStatement' && statement.alternate == null; | ||
} | ||
|
||
function isOffendingConsequent(consequent) { | ||
return ( | ||
(consequent.type === 'ExpressionStatement' && maxStatements === 0) | ||
|| (consequent.type === 'BlockStatement' | ||
&& consequent.body.length > maxStatements) | ||
); | ||
} | ||
|
||
function isOffendingIfStatement(statement) { | ||
return ( | ||
isLonelyIfStatement(statement) | ||
&& isOffendingConsequent(statement.consequent) | ||
); | ||
} | ||
|
||
function hasSimplifiableConditionalBody(functionBody) { | ||
const body = functionBody.body; | ||
return ( | ||
functionBody.type === 'BlockStatement' | ||
&& body.length === 1 | ||
&& isOffendingIfStatement(body[0]) | ||
); | ||
} | ||
|
||
function checkFunctionBody(functionNode) { | ||
const body = functionNode.body; | ||
|
||
if (hasSimplifiableConditionalBody(body)) { | ||
context.report( | ||
body, | ||
message, | ||
); | ||
} | ||
} | ||
|
||
return { | ||
FunctionDeclaration: checkFunctionBody, | ||
FunctionExpression: checkFunctionBody, | ||
ArrowFunctionExpression: checkFunctionBody, | ||
}; | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
const _ = require('underscore'); | ||
const lodashGet = require('lodash/get'); | ||
const message = require('./CONST').MESSAGE.PREFER_IMPORT_MODULE_CONTENTS; | ||
|
||
/** | ||
* @param {String} source | ||
* @returns {Boolean} | ||
*/ | ||
function isFromNodeModules(source) { | ||
return !source.startsWith('.') && !source.startsWith('..'); | ||
} | ||
|
||
/** | ||
* @param {Array} specifiers | ||
* @returns {Boolean} | ||
*/ | ||
function isEverySpecifierImport(specifiers = []) { | ||
return _.every(specifiers, specifier => specifier.type === 'ImportSpecifier'); | ||
} | ||
|
||
module.exports = { | ||
create: context => ({ | ||
ImportDeclaration(node) { | ||
const sourceValue = lodashGet(node, 'source.value'); | ||
if (!sourceValue) { | ||
return; | ||
} | ||
|
||
if (isFromNodeModules(sourceValue)) { | ||
return; | ||
} | ||
|
||
if (!node.specifiers || !node.specifiers.length) { | ||
return; | ||
} | ||
|
||
if (!isEverySpecifierImport(node.specifiers)) { | ||
return; | ||
} | ||
|
||
context.report({ | ||
node, | ||
message, | ||
}); | ||
}, | ||
}), | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
const lodashGet = require('lodash/get'); | ||
const {isOnyxMethodCall, isInTestFile} = require('./utils'); | ||
|
||
const message = require('./CONST').MESSAGE.PREFER_ONYX_CONNECT_IN_LIBS; | ||
|
||
/** | ||
* @param {String} filename | ||
* @returns {Boolean} | ||
*/ | ||
function isInLibs(filename) { | ||
return filename.includes('/src/libs/'); | ||
} | ||
|
||
module.exports = { | ||
create: context => ({ | ||
MemberExpression(node) { | ||
const filename = context.getFilename(); | ||
|
||
if (!isOnyxMethodCall(node)) { | ||
return; | ||
} | ||
|
||
if (isInTestFile(context.getFilename())) { | ||
return; | ||
} | ||
|
||
const methodName = lodashGet(node, 'property.name'); | ||
if (methodName !== 'connect' || isInLibs(filename)) { | ||
return; | ||
} | ||
|
||
context.report({ | ||
node, | ||
message, | ||
}); | ||
}, | ||
}), | ||
}; |
Oops, something went wrong.