diff --git a/eslint-plugin-expensify/CONST.js b/eslint-plugin-expensify/CONST.js index 9c273b5..456be1f 100644 --- a/eslint-plugin-expensify/CONST.js +++ b/eslint-plugin-expensify/CONST.js @@ -8,6 +8,7 @@ module.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', + NO_MULTIPLE_ONYX_IN_FILE: 'Only use withOnyx() once. Read here about how to properly work with dependent Onyx keys: https://github.com/Expensify/react-native-onyx#dependent-onyx-keys-and-withonyx', 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.', diff --git a/eslint-plugin-expensify/no-multiple-onyx-in-file.js b/eslint-plugin-expensify/no-multiple-onyx-in-file.js new file mode 100644 index 0000000..e22ddd8 --- /dev/null +++ b/eslint-plugin-expensify/no-multiple-onyx-in-file.js @@ -0,0 +1,28 @@ +const lodashGet = require('lodash/get'); +const message = require('./CONST').MESSAGE.NO_MULTIPLE_ONYX_IN_FILE; + +module.exports = { + create: (context) => { + let withOnyxCount = 0; + + return { + CallExpression(node) { + const calleeName = lodashGet(node, 'callee.name'); + + if (calleeName === 'withOnyx') { + withOnyxCount += 1; + + if (withOnyxCount > 1) { + context.report({ + node, + message, + }); + } + } + }, + 'Program:exit': () => { + withOnyxCount = 0; + }, + }; + }, +}; diff --git a/eslint-plugin-expensify/tests/no-multiple-onyx-in-file.test.js b/eslint-plugin-expensify/tests/no-multiple-onyx-in-file.test.js new file mode 100644 index 0000000..340074c --- /dev/null +++ b/eslint-plugin-expensify/tests/no-multiple-onyx-in-file.test.js @@ -0,0 +1,43 @@ +const RuleTester = require('eslint').RuleTester; +const rule = require('../no-multiple-onyx-in-file'); +const message = require('../CONST').MESSAGE.NO_MULTIPLE_ONYX_IN_FILE; + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + }, +}); + +ruleTester.run('no-multiple-onyx-in-file', rule, { + valid: [ + { + code: ` + compose( + withOnyx({ + key: 'value', + }) + )(Component); + `, + }, + ], + invalid: [ + { + code: ` + compose( + withOnyx({ + key1: 'value1', + }), + withOnyx({ + key1: 'value1', + }) + )(Component); + `, + errors: [ + { + message, + }, + ], + }, + ], +}); diff --git a/package.json b/package.json index 73d480f..298eb7e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-expensify", - "version": "2.0.38", + "version": "2.0.39", "description": "Expensify's ESLint configuration following our style guide", "main": "index.js", "repository": { diff --git a/rules/expensify.js b/rules/expensify.js index e447efa..e9fc647 100644 --- a/rules/expensify.js +++ b/rules/expensify.js @@ -11,6 +11,7 @@ module.exports = { 'rulesdir/no-useless-compose': 'error', 'rulesdir/prefer-import-module-contents': 'error', 'rulesdir/no-multiple-api-calls': 'error', + 'rulesdir/no-multiple-onyx-in-file': 'error', 'rulesdir/no-call-actions-from-actions': 'error', 'rulesdir/no-api-side-effects-method': 'error', 'rulesdir/display-name-property': 'error',