Skip to content

Commit 07232ad

Browse files
committed
Support createClass in no-typos
Resolves #1721
1 parent d68b9e8 commit 07232ad

File tree

2 files changed

+241
-29
lines changed

2 files changed

+241
-29
lines changed

lib/rules/no-typos.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,18 @@ module.exports = {
124124
}
125125
}
126126

127-
function reportErrorIfClassPropertyCasingTypo(node, propertyName) {
127+
function reportErrorIfPropertyCasingTypo(node, propertyName, isClassProperty) {
128128
if (propertyName === 'propTypes' || propertyName === 'contextTypes' || propertyName === 'childContextTypes') {
129129
checkValidPropObject(node);
130130
}
131131
STATIC_CLASS_PROPERTIES.forEach(CLASS_PROP => {
132132
if (propertyName && CLASS_PROP.toLowerCase() === propertyName.toLowerCase() && CLASS_PROP !== propertyName) {
133+
const message = isClassProperty
134+
? 'Typo in static class property declaration'
135+
: 'Typo in property declaration';
133136
context.report({
134137
node: node,
135-
message: 'Typo in static class property declaration'
138+
message: message
136139
});
137140
}
138141
});
@@ -175,7 +178,7 @@ module.exports = {
175178

176179
const tokens = context.getFirstTokens(node, 2);
177180
const propertyName = tokens[1].value;
178-
reportErrorIfClassPropertyCasingTypo(node.value, propertyName);
181+
reportErrorIfPropertyCasingTypo(node.value, propertyName, true);
179182
},
180183

181184
MemberExpression: function(node) {
@@ -195,16 +198,29 @@ module.exports = {
195198
(utils.isES6Component(relatedComponent.node) || utils.isReturningJSX(relatedComponent.node)) &&
196199
(node.parent && node.parent.type === 'AssignmentExpression' && node.parent.right)
197200
) {
198-
reportErrorIfClassPropertyCasingTypo(node.parent.right, propertyName);
201+
reportErrorIfPropertyCasingTypo(node.parent.right, propertyName, true);
199202
}
200203
},
201204

202-
MethodDefinition: function (node) {
205+
MethodDefinition: function(node) {
203206
if (!utils.isES6Component(node.parent.parent)) {
204207
return;
205208
}
206209

207210
reportErrorIfLifecycleMethodCasingTypo(node);
211+
},
212+
213+
ObjectExpression: function(node) {
214+
const component = utils.isES5Component(node) && components.get(node);
215+
216+
if (!component) {
217+
return;
218+
}
219+
220+
node.properties.forEach(property => {
221+
reportErrorIfPropertyCasingTypo(property.value, property.key.name, false);
222+
reportErrorIfLifecycleMethodCasingTypo(property);
223+
});
208224
}
209225
};
210226
})

tests/lib/rules/no-typos.js

Lines changed: 220 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const parserOptions = {
2323
// -----------------------------------------------------------------------------
2424

2525
const ERROR_MESSAGE = 'Typo in static class property declaration';
26+
const ERROR_MESSAGE_ES5 = 'Typo in property declaration';
2627
const ERROR_MESSAGE_LIFECYCLE_METHOD = 'Typo in component lifecycle method declaration';
2728

2829
const ruleTester = new RuleTester();
@@ -519,6 +520,105 @@ ruleTester.run('no-typos', rule, {
519520
`,
520521
parser: 'babel-eslint',
521522
parserOptions: parserOptions
523+
}, {
524+
code: `
525+
import React from 'react';
526+
import PropTypes from 'prop-types';
527+
const Component = React.createReactClass({
528+
propTypes: {
529+
a: PropTypes.string.isRequired,
530+
b: PropTypes.shape({
531+
c: PropTypes.number
532+
}).isRequired
533+
}
534+
});
535+
`,
536+
parserOptions: parserOptions
537+
}, {
538+
code: `
539+
import React from 'react';
540+
import PropTypes from 'prop-types';
541+
const Component = React.createReactClass({
542+
propTypes: {
543+
a: PropTypes.string.isRequired,
544+
b: PropTypes.shape({
545+
c: PropTypes.number
546+
}).isRequired
547+
}
548+
});
549+
`,
550+
parser: 'babel-eslint',
551+
parserOptions: parserOptions
552+
}, {
553+
code: `
554+
import React from 'react';
555+
import PropTypes from 'prop-types';
556+
const Component = React.createReactClass({
557+
childContextTypes: {
558+
a: PropTypes.bool,
559+
b: PropTypes.array,
560+
c: PropTypes.func,
561+
d: PropTypes.object,
562+
}
563+
});
564+
`,
565+
parserOptions: parserOptions
566+
}, {
567+
code: `
568+
import React from 'react';
569+
import PropTypes from 'prop-types';
570+
const Component = React.createReactClass({
571+
childContextTypes: {
572+
a: PropTypes.bool,
573+
b: PropTypes.array,
574+
c: PropTypes.func,
575+
d: PropTypes.object,
576+
}
577+
});
578+
`,
579+
parser: 'babel-eslint',
580+
parserOptions: parserOptions
581+
}, {
582+
code: `
583+
import React from 'react';
584+
const Component = React.createReactClass({
585+
propTypes: {},
586+
childContextTypes: {},
587+
contextTypes: {},
588+
componentWillMount() { },
589+
componentDidMount() { },
590+
componentWillReceiveProps() { },
591+
shouldComponentUpdate() { },
592+
componentWillUpdate() { },
593+
componentDidUpdate() { },
594+
componentWillUnmount() { },
595+
render() {
596+
return <div>Hello {this.props.name}</div>;
597+
}
598+
});
599+
`,
600+
parserOptions: parserOptions
601+
}, {
602+
code: `
603+
import React from 'react';
604+
const Component = React.createReactClass({
605+
propTypes: {},
606+
childContextTypes: {},
607+
contextTypes: {},
608+
componentWillMount() { },
609+
componentDidMount() { },
610+
componentWillReceiveProps() { },
611+
shouldComponentUpdate() { },
612+
componentWillUpdate() { },
613+
componentDidUpdate() { },
614+
componentWillUnmount() { },
615+
render() {
616+
return <div>Hello {this.props.name}</div>;
617+
}
618+
});
619+
`,
620+
parser: 'babel-eslint',
621+
parserOptions: parserOptions
522622
}],
523623

524624
invalid: [{
@@ -1369,28 +1469,11 @@ ruleTester.run('no-typos', rule, {
13691469
}, {
13701470
message: 'Typo in declared prop type: objectof'
13711471
}]
1372-
}]
1373-
/*
1374-
// PropTypes declared on a component that is detected through JSDoc comments and is
1375-
// declared AFTER the PropTypes assignment
1376-
// Commented out since it only works with ESLint 5.
1377-
,{
1378-
code: `
1379-
MyComponent.PROPTYPES = {}
1380-
\/** @extends React.Component *\/
1381-
class MyComponent extends BaseComponent {}
1382-
`,
1383-
parserOptions: parserOptions
1384-
},
1385-
*/
1386-
/*
1387-
// createClass tests below fail, so they're commented out
1388-
// ---------
13891472
}, {
13901473
code: `
13911474
import React from 'react';
13921475
import PropTypes from 'prop-types';
1393-
const Component = React.createClass({
1476+
const Component = React.createReactClass({
13941477
propTypes: {
13951478
a: PropTypes.string.isrequired,
13961479
b: PropTypes.shape({
@@ -1410,7 +1493,7 @@ ruleTester.run('no-typos', rule, {
14101493
code: `
14111494
import React from 'react';
14121495
import PropTypes from 'prop-types';
1413-
const Component = React.createClass({
1496+
const Component = React.createReactClass({
14141497
childContextTypes: {
14151498
a: PropTypes.bools,
14161499
b: PropTypes.Array,
@@ -1434,7 +1517,7 @@ ruleTester.run('no-typos', rule, {
14341517
code: `
14351518
import React from 'react';
14361519
import PropTypes from 'prop-types';
1437-
const Component = React.createClass({
1520+
const Component = React.createReactClass({
14381521
propTypes: {
14391522
a: PropTypes.string.isrequired,
14401523
b: PropTypes.shape({
@@ -1453,7 +1536,7 @@ ruleTester.run('no-typos', rule, {
14531536
code: `
14541537
import React from 'react';
14551538
import PropTypes from 'prop-types';
1456-
const Component = React.createClass({
1539+
const Component = React.createReactClass({
14571540
childContextTypes: {
14581541
a: PropTypes.bools,
14591542
b: PropTypes.Array,
@@ -1472,8 +1555,121 @@ ruleTester.run('no-typos', rule, {
14721555
}, {
14731556
message: 'Typo in declared prop type: objectof'
14741557
}]
1558+
}, {
1559+
code: `
1560+
import React from 'react';
1561+
const Component = React.createReactClass({
1562+
proptypes: {},
1563+
childcontexttypes: {},
1564+
contexttypes: {},
1565+
ComponentWillMount() { },
1566+
ComponentDidMount() { },
1567+
ComponentWillReceiveProps() { },
1568+
ShouldComponentUpdate() { },
1569+
ComponentWillUpdate() { },
1570+
ComponentDidUpdate() { },
1571+
ComponentWillUnmount() { },
1572+
render() {
1573+
return <div>Hello {this.props.name}</div>;
1574+
}
1575+
});
1576+
`,
1577+
parserOptions: parserOptions,
1578+
errors: [{
1579+
message: ERROR_MESSAGE_ES5,
1580+
type: 'ObjectExpression'
1581+
}, {
1582+
message: ERROR_MESSAGE_ES5,
1583+
type: 'ObjectExpression'
1584+
}, {
1585+
message: ERROR_MESSAGE_ES5,
1586+
type: 'ObjectExpression'
1587+
}, {
1588+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
1589+
type: 'Property'
1590+
}, {
1591+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
1592+
type: 'Property'
1593+
}, {
1594+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
1595+
type: 'Property'
1596+
}, {
1597+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
1598+
type: 'Property'
1599+
}, {
1600+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
1601+
type: 'Property'
1602+
}, {
1603+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
1604+
type: 'Property'
1605+
}, {
1606+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
1607+
type: 'Property'
1608+
}]
1609+
}, {
1610+
code: `
1611+
import React from 'react';
1612+
const Component = React.createReactClass({
1613+
proptypes: {},
1614+
childcontexttypes: {},
1615+
contexttypes: {},
1616+
ComponentWillMount() { },
1617+
ComponentDidMount() { },
1618+
ComponentWillReceiveProps() { },
1619+
ShouldComponentUpdate() { },
1620+
ComponentWillUpdate() { },
1621+
ComponentDidUpdate() { },
1622+
ComponentWillUnmount() { },
1623+
render() {
1624+
return <div>Hello {this.props.name}</div>;
1625+
}
1626+
});
1627+
`,
1628+
parser: 'babel-eslint',
1629+
parserOptions: parserOptions,
1630+
errors: [{
1631+
message: ERROR_MESSAGE_ES5,
1632+
type: 'ObjectExpression'
1633+
}, {
1634+
message: ERROR_MESSAGE_ES5,
1635+
type: 'ObjectExpression'
1636+
}, {
1637+
message: ERROR_MESSAGE_ES5,
1638+
type: 'ObjectExpression'
1639+
}, {
1640+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
1641+
type: 'Property'
1642+
}, {
1643+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
1644+
type: 'Property'
1645+
}, {
1646+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
1647+
type: 'Property'
1648+
}, {
1649+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
1650+
type: 'Property'
1651+
}, {
1652+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
1653+
type: 'Property'
1654+
}, {
1655+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
1656+
type: 'Property'
1657+
}, {
1658+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
1659+
type: 'Property'
1660+
}]
1661+
/*
1662+
// PropTypes declared on a component that is detected through JSDoc comments and is
1663+
// declared AFTER the PropTypes assignment
1664+
// Commented out since it only works with ESLint 5.
1665+
,{
1666+
code: `
1667+
MyComponent.PROPTYPES = {}
1668+
\/** @extends React.Component *\/
1669+
class MyComponent extends BaseComponent {}
1670+
`,
1671+
parserOptions: parserOptions
1672+
},
1673+
*/
14751674
}]
1476-
// ---------
1477-
// createClass tests above fail, so they're commented out
1478-
*/
14791675
});

0 commit comments

Comments
 (0)