Skip to content

Commit

Permalink
Merge pull request #83 from ishpaul777/lint/prohibit-default-props
Browse files Browse the repository at this point in the history
  • Loading branch information
roryabraham committed Dec 23, 2023
2 parents 17dbfa4 + f9a5a46 commit 5155010
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 0 deletions.
1 change: 1 addition & 0 deletions eslint-plugin-expensify/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ module.exports = {
HAVE_DEFAULT_PROPS: 'Component must have default prop values.',
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.',
},
};
58 changes: 58 additions & 0 deletions eslint-plugin-expensify/no-default-props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const _ = require('underscore');
const lodashGet = require('lodash/get');
const message = require('./CONST').MESSAGE.NO_DEFAULT_PROPS;
const {isReactViewFile} = require('./utils');

module.exports = {
create: context => ({
AssignmentExpression(node) {
// Only looking at react files
if (!isReactViewFile(context.getFilename())) {
return;
}

// if the name of assignment is not defaultProps, we're good
if (lodashGet(node, 'left.property.name') !== 'defaultProps') {
return;
}

// Find all the function in the parent node that returns a jsx element
const parent = lodashGet(node, 'parent.type') === 'FunctionDeclaration' ? node.parent : lodashGet(node, 'parent.parent');
const functionComponents = _.filter(parent.body, (n) => {
if (['FunctionDeclaration', 'ExportNamedDeclaration'].indexOf(n.type) === -1) {
return false;
}
const body = n.type === 'ExportNamedDeclaration' ? lodashGet(n, 'declaration.body.body') : lodashGet(n, 'body.body');
const isReturningJSX = _.filter(body, bodyNode => bodyNode.type === 'ReturnStatement' && lodashGet(bodyNode, 'argument.type') === 'JSXElement');
if (_.isEmpty(isReturningJSX)) {
return false;
}
return true;
});

// If we don't have any function components, we're good
if (_.isEmpty(functionComponents)) {
return;
}

// Find all the function component names
const functionComponentNames = _.map(functionComponents, (functionComponent) => {
if (functionComponent.type === 'FunctionDeclaration') {
return functionComponent.id.name;
}
return lodashGet(functionComponent, 'declaration.id.name');
});

// check if the function component names includes the name of the object
if (!_.includes(functionComponentNames, lodashGet(node, 'left.object.name'))) {
return;
}

// report the error
context.report({
node,
message,
});
},
}),
};
153 changes: 153 additions & 0 deletions eslint-plugin-expensify/tests/no-default-props.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
const RuleTester = require('eslint').RuleTester;
const rule = require('../no-default-props');
const message = require('../CONST').MESSAGE.NO_DEFAULT_PROPS;

const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
// To support use of < in HOC
jsx: true,

// To support use of ... operator
experimentalObjectRestSpread: true,
},
},
});

ruleTester.run('no-default-props', rule, {
valid: [
{
code: `
function Test({ propWithDefault = 'defaultValue' }) {
return <div>{propWithDefault}</div>;
}`,
filename: '/src/components/Test.js',
},
{
code: `
function Test({ propWithDefault = 'defaultValue' }) {
return <div>{propWithDefault}</div>;
}
Test.displayName = 'Test';`,
filename: '/src/components/Test.js',
},
{
// VALID AS TECHNICALLY THIS IS NOT A FUNCTION DECLARATION
code: `
const Test = React.forwardRef((props, ref) => {
return <div>{props.propWithDefault}</div>;
});
Test.defaultProps = { propWithDefault: 'defaultValue' };`,
filename: '/src/pages/Test.js',
},
{
// VALID AS TECHNICALLY THIS IS NOT A FUNCTION DECLARATION
code: `
const Test = React.memo((props) => {
return <div>{props.propWithDefault}</div>;
});
Test.defaultProps = { propWithDefault: 'defaultValue' };`,
filename: '/src/pages/Test.js',
},
{
code: `
class Test extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>{this.props.propWithDefault}</div>;
}
}
Test.defaultProps = { propWithDefault: 'defaultValue' };`,
filename: '/src/pages/Test.js',
},
{
code: `function Test(props) {
return <div>{props.propWithDefault}</div>;
}
Test.displayName = 'Test';
Test.defaultProps = { propWithDefault: 'defaultValue' };`,
filename: '/src/libs/Test.js',
},
],
invalid: [
{
code: `
function Test({ propWithDefault = 'defaultValue' }) {
return <div>{props.propWithDefault}</div>;
}
Test.defaultProps = { propWithDefault: 'defaultValue' };`,
filename: '/src/components/Test.js',
errors: [{message}],
},
{
code: `
function Test(props) {
return <div>{props.propWithDefault}</div>;
}
Test.displayName = 'Test';
Test.defaultProps = { propWithDefault: 'defaultValue' };`,
filename: '/src/pages/Test.js',
errors: [{message}],
},
{
code: `function Test(props, ref) {
return <div>{props.propWithDefault}</div>;
}
Test.defaultProps = { propWithDefault: 'defaultValue' };
export default React.forwardRef(Test);`,
filename: '/src/pages/Test.js',
errors: [{message}],
},
{
code: `function Test(props) {
return <div>{props.propWithDefault}</div>;
}
Test.defaultProps = { propWithDefault: 'defaultValue' };
export default React.memo(Test);`,
filename: '/src/pages/Test.js',
errors: [{message}],
},
{
code: `
function HOC(Component) {
function WrappedComponent({propWithDefault, ...props}) {
return <Component {...props} />;
}
WrappedComponent.defaultProps = { propWithDefault: 'defaultValue' };
return WrappedComponent;
}`,
filename: '/src/pages/Test.js',
errors: [{message}],
},
{
code: `
function HOC(Component) {
function WrappedComponent({propWithDefault, ...props}) {
return <Component {...props} />;
}
WrappedComponent.defaultProps = { propWithDefault: 'defaultValue' };
return WrappedComponent;
}
function Test(props) {
return <div>{props.propWithDefault}</div>;
}
Test.defaultProps = { propWithDefault: 'defaultValue' };
export default HOC(Test);`,
filename: '/src/pages/Test.js',
errors: [{message}, {message}],
},
{
code: `
export function Test(props) {
return <div>{props.propWithDefault}</div>;
}
Test.defaultProps = { propWithDefault: 'defaultValue' };`,
filename: '/src/pages/Test.js',
errors: [{message}],
},
],
});

0 comments on commit 5155010

Please sign in to comment.