diff --git a/eslint-plugin-expensify/CONST.js b/eslint-plugin-expensify/CONST.js index abfc6dc..8ed4156 100644 --- a/eslint-plugin-expensify/CONST.js +++ b/eslint-plugin-expensify/CONST.js @@ -27,5 +27,6 @@ module.exports = { ONYX_ONE_PARAM: 'The withOnyx HOC must be passed at least one argument.', MUST_USE_VARIABLE_FOR_ASSIGNMENT: '{{key}} must be assigned as a variable instead of direct assignment.', NO_DEFAULT_PROPS: 'defaultProps should not be used in function components. Use default Arguments instead.', + AVOID_ANONYMOUS_FUNCTIONS: 'Prefer named functions.', }, }; diff --git a/eslint-plugin-expensify/avoid-anonymous-functions.js b/eslint-plugin-expensify/avoid-anonymous-functions.js new file mode 100644 index 0000000..b3f9db7 --- /dev/null +++ b/eslint-plugin-expensify/avoid-anonymous-functions.js @@ -0,0 +1,32 @@ +const message = require('./CONST').MESSAGE.AVOID_ANONYMOUS_FUNCTIONS; + +module.exports = { + create(context) { + return { + "CallExpression > FunctionExpression": function (node) { + if (!node.id && !node.generator && !node.async) { + context.report({ + node, + message, + }); + } + }, + "CallExpression": function (node) { + if (node.arguments && node.arguments.some(arg => arg.type === "ArrowFunctionExpression" && !arg.id)) { + context.report({ + node, + message, + }); + } + }, + "ReturnStatement > FunctionExpression, ReturnStatement > ArrowFunctionExpression": function (node) { + if (!node.id) { + context.report({ + node, + message, + }); + } + } + }; + }, +}; diff --git a/eslint-plugin-expensify/tests/avoid-anonymous-functions.test.js b/eslint-plugin-expensify/tests/avoid-anonymous-functions.test.js new file mode 100644 index 0000000..a2aaee9 --- /dev/null +++ b/eslint-plugin-expensify/tests/avoid-anonymous-functions.test.js @@ -0,0 +1,226 @@ +const RuleTester = require('eslint').RuleTester; +const rule = require('../avoid-anonymous-functions'); +const message = require('../CONST').MESSAGE.AVOID_ANONYMOUS_FUNCTIONS; + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + }, +}); + +ruleTester.run('avoid-anonymous-functions', rule, { + valid: [ + { + code: ` + function test() { + function innerFunction(node) { + return node.isParent; + } + + const onlyParents = nodes.filter(innerFunction); + + return onlyParents; + }`, + }, + { + code: ` + function test() { + const onlyParents = nodes.filter(function innerFunction(node) { + return node.isParent; + }); + + return onlyParents; + }`, + }, + { + code: ` + function test() { + const node = {execute: function named() {}}; + useEffect(function innerFunction() { + node.execute(); + }, []); + return true; + }`, + }, + { + code: ` + function test() { + const node = {execute: () => {}}; + useEffect(function* () { + node.execute(); + }, []); + return true; + }`, + }, + { + code: ` + function test() { + const node = {execute: function named() {}}; + useEffect(node.execute, []); + return true; + } + ` + }, + { + code: ` + function test() { + const node = {execute: () => {}}; + useEffect(node.execute, []); + return true; + } + ` + }, + { + code: ` + function test() { + const node = () => {}; + useEffect(node, []); + return true; + } + ` + }, + { + code: ` + function test() { + const filteringById = () => {}; + parents.filter(filteringById); + return true; + } + ` + }, + { + code: ` + function test() { + function withName() { + return function innerName() {}; + } + withName(); + return true; + } + ` + }, + ], + invalid: [ + { + code: ` + function test() { + const onlyParents = nodes.filter((node) => node.isParent); + + return onlyParents; + } + `, + errors: [{ + message, + }], + }, + { + code: ` + function test() { + const onlyParents = nodes.filter((node) => { + return node.isParent; + }); + + return onlyParents; + } + `, + errors: [{ + message, + }], + }, + { + code: ` + function test() { + const node = {execute: function named() {}}; + useEffect(function () { + node.execute(); + }, []); + return true; + } + `, + errors: [{ + message, + }], + }, + { + code: ` + function test() { + const node = {execute: function named() {}}; + useEffect(() => node.execute(), []); + return true; + } + `, + errors: [{ + message, + }], + }, + { + code: ` + function test() { + const node = {execute: () => {}}; + useEffect(function () {node.execute();} , []); + return true; + } + `, + errors: [{ + message, + }], + }, + { + code: ` + function test() { + function rightNamed() { + return () => {}; + } + rightNamed(); + return true; + } + `, + errors: [{ + message, + }], + }, + { + code: ` + function test() { + function rightNamed() { + return function () {}; + } + rightNamed(); + return true; + } + `, + errors: [{ + message, + }], + }, + { + code: ` + function test() { + const rightNamed = () => { + return function () {}; + } + rightNamed(); + return true; + } + `, + errors: [{ + message, + }], + }, + { + code: ` + function test() { + const rightNamed = () => { + return () => {}; + } + rightNamed(); + return true; + } + `, + errors: [{ + message, + }], + }, + ], +}); diff --git a/rules/expensify.js b/rules/expensify.js index 1934a49..d8d389a 100644 --- a/rules/expensify.js +++ b/rules/expensify.js @@ -16,6 +16,7 @@ module.exports = { 'rulesdir/no-api-side-effects-method': 'error', 'rulesdir/prefer-localization': 'error', 'rulesdir/onyx-props-must-have-default': 'error', + 'rulesdir/avoid-anonymous-functions': 'error', 'no-restricted-imports': ['error', { paths: [{ name: 'react-native', @@ -28,4 +29,4 @@ module.exports = { }], }], }, -}; +}; \ No newline at end of file