diff --git a/lib/rules/no-typos.js b/lib/rules/no-typos.js
index 4540e5a015..47e2b7fdf1 100644
--- a/lib/rules/no-typos.js
+++ b/lib/rules/no-typos.js
@@ -124,15 +124,18 @@ module.exports = {
}
}
- function reportErrorIfClassPropertyCasingTypo(node, propertyName) {
+ function reportErrorIfPropertyCasingTypo(node, propertyName, isClassProperty) {
if (propertyName === 'propTypes' || propertyName === 'contextTypes' || propertyName === 'childContextTypes') {
checkValidPropObject(node);
}
STATIC_CLASS_PROPERTIES.forEach(CLASS_PROP => {
if (propertyName && CLASS_PROP.toLowerCase() === propertyName.toLowerCase() && CLASS_PROP !== propertyName) {
+ const message = isClassProperty
+ ? 'Typo in static class property declaration'
+ : 'Typo in property declaration';
context.report({
node: node,
- message: 'Typo in static class property declaration'
+ message: message
});
}
});
@@ -175,7 +178,7 @@ module.exports = {
const tokens = context.getFirstTokens(node, 2);
const propertyName = tokens[1].value;
- reportErrorIfClassPropertyCasingTypo(node.value, propertyName);
+ reportErrorIfPropertyCasingTypo(node.value, propertyName, true);
},
MemberExpression: function(node) {
@@ -195,16 +198,29 @@ module.exports = {
(utils.isES6Component(relatedComponent.node) || utils.isReturningJSX(relatedComponent.node)) &&
(node.parent && node.parent.type === 'AssignmentExpression' && node.parent.right)
) {
- reportErrorIfClassPropertyCasingTypo(node.parent.right, propertyName);
+ reportErrorIfPropertyCasingTypo(node.parent.right, propertyName, true);
}
},
- MethodDefinition: function (node) {
+ MethodDefinition: function(node) {
if (!utils.isES6Component(node.parent.parent)) {
return;
}
reportErrorIfLifecycleMethodCasingTypo(node);
+ },
+
+ ObjectExpression: function(node) {
+ const component = utils.isES5Component(node) && components.get(node);
+
+ if (!component) {
+ return;
+ }
+
+ node.properties.forEach(property => {
+ reportErrorIfPropertyCasingTypo(property.value, property.key.name, false);
+ reportErrorIfLifecycleMethodCasingTypo(property);
+ });
}
};
})
diff --git a/tests/lib/rules/no-typos.js b/tests/lib/rules/no-typos.js
index 06279e786b..3f1c472097 100644
--- a/tests/lib/rules/no-typos.js
+++ b/tests/lib/rules/no-typos.js
@@ -23,6 +23,7 @@ const parserOptions = {
// -----------------------------------------------------------------------------
const ERROR_MESSAGE = 'Typo in static class property declaration';
+const ERROR_MESSAGE_ES5 = 'Typo in property declaration';
const ERROR_MESSAGE_LIFECYCLE_METHOD = 'Typo in component lifecycle method declaration';
const ruleTester = new RuleTester();
@@ -519,6 +520,105 @@ ruleTester.run('no-typos', rule, {
`,
parser: 'babel-eslint',
parserOptions: parserOptions
+ }, {
+ code: `
+ import React from 'react';
+ import PropTypes from 'prop-types';
+ const Component = React.createReactClass({
+ propTypes: {
+ a: PropTypes.string.isRequired,
+ b: PropTypes.shape({
+ c: PropTypes.number
+ }).isRequired
+ }
+ });
+ `,
+ parserOptions: parserOptions
+ }, {
+ code: `
+ import React from 'react';
+ import PropTypes from 'prop-types';
+ const Component = React.createReactClass({
+ propTypes: {
+ a: PropTypes.string.isRequired,
+ b: PropTypes.shape({
+ c: PropTypes.number
+ }).isRequired
+ }
+ });
+ `,
+ parser: 'babel-eslint',
+ parserOptions: parserOptions
+ }, {
+ code: `
+ import React from 'react';
+ import PropTypes from 'prop-types';
+ const Component = React.createReactClass({
+ childContextTypes: {
+ a: PropTypes.bool,
+ b: PropTypes.array,
+ c: PropTypes.func,
+ d: PropTypes.object,
+ }
+ });
+ `,
+ parserOptions: parserOptions
+ }, {
+ code: `
+ import React from 'react';
+ import PropTypes from 'prop-types';
+ const Component = React.createReactClass({
+ childContextTypes: {
+ a: PropTypes.bool,
+ b: PropTypes.array,
+ c: PropTypes.func,
+ d: PropTypes.object,
+ }
+ });
+ `,
+ parser: 'babel-eslint',
+ parserOptions: parserOptions
+ }, {
+ code: `
+ import React from 'react';
+ const Component = React.createReactClass({
+ propTypes: {},
+ childContextTypes: {},
+ contextTypes: {},
+ componentWillMount() { },
+ componentDidMount() { },
+ componentWillReceiveProps() { },
+ shouldComponentUpdate() { },
+ componentWillUpdate() { },
+ componentDidUpdate() { },
+ componentWillUnmount() { },
+ render() {
+ return
Hello {this.props.name}
;
+ }
+ });
+ `,
+ parserOptions: parserOptions
+ }, {
+ code: `
+ import React from 'react';
+ const Component = React.createReactClass({
+ propTypes: {},
+ childContextTypes: {},
+ contextTypes: {},
+ componentWillMount() { },
+ componentDidMount() { },
+ componentWillReceiveProps() { },
+ shouldComponentUpdate() { },
+ componentWillUpdate() { },
+ componentDidUpdate() { },
+ componentWillUnmount() { },
+ render() {
+ return Hello {this.props.name}
;
+ }
+ });
+ `,
+ parser: 'babel-eslint',
+ parserOptions: parserOptions
}],
invalid: [{
@@ -1369,111 +1469,207 @@ ruleTester.run('no-typos', rule, {
}, {
message: 'Typo in declared prop type: objectof'
}]
- }]
- /*
- // PropTypes declared on a component that is detected through JSDoc comments and is
- // declared AFTER the PropTypes assignment
- // Commented out since it only works with ESLint 5.
- ,{
- code: `
- MyComponent.PROPTYPES = {}
- \/** @extends React.Component *\/
- class MyComponent extends BaseComponent {}
- `,
- parserOptions: parserOptions
- },
- */
- /*
- // createClass tests below fail, so they're commented out
- // ---------
- }, {
- code: `
- import React from 'react';
- import PropTypes from 'prop-types';
- const Component = React.createClass({
- propTypes: {
- a: PropTypes.string.isrequired,
- b: PropTypes.shape({
- c: PropTypes.number
- }).isrequired
- }
- });
- `,
- parser: 'babel-eslint',
- parserOptions: parserOptions,
- errors: [{
- message: 'Typo in prop type chain qualifier: isrequired'
- }, {
- message: 'Typo in prop type chain qualifier: isrequired'
- }]
- }, {
- code: `
- import React from 'react';
- import PropTypes from 'prop-types';
- const Component = React.createClass({
- childContextTypes: {
- a: PropTypes.bools,
- b: PropTypes.Array,
- c: PropTypes.function,
- d: PropTypes.objectof,
- }
- });
- `,
- parser: 'babel-eslint',
- parserOptions: parserOptions,
- errors: [{
- message: 'Typo in declared prop type: bools'
- }, {
- message: 'Typo in declared prop type: Array'
- }, {
- message: 'Typo in declared prop type: function'
- }, {
- message: 'Typo in declared prop type: objectof'
- }]
- }, {
- code: `
- import React from 'react';
- import PropTypes from 'prop-types';
- const Component = React.createClass({
- propTypes: {
- a: PropTypes.string.isrequired,
- b: PropTypes.shape({
- c: PropTypes.number
- }).isrequired
- }
- });
- `,
- parserOptions: parserOptions,
- errors: [{
- message: 'Typo in prop type chain qualifier: isrequired'
- }, {
- message: 'Typo in prop type chain qualifier: isrequired'
- }]
- }, {
- code: `
- import React from 'react';
- import PropTypes from 'prop-types';
- const Component = React.createClass({
- childContextTypes: {
- a: PropTypes.bools,
- b: PropTypes.Array,
- c: PropTypes.function,
- d: PropTypes.objectof,
- }
- });
- `,
- parserOptions: parserOptions,
- errors: [{
- message: 'Typo in declared prop type: bools'
- }, {
- message: 'Typo in declared prop type: Array'
- }, {
- message: 'Typo in declared prop type: function'
- }, {
- message: 'Typo in declared prop type: objectof'
- }]
+ }, {
+ code: `
+ import React from 'react';
+ import PropTypes from 'prop-types';
+ const Component = React.createReactClass({
+ propTypes: {
+ a: PropTypes.string.isrequired,
+ b: PropTypes.shape({
+ c: PropTypes.number
+ }).isrequired
+ }
+ });
+ `,
+ parser: 'babel-eslint',
+ parserOptions: parserOptions,
+ errors: [{
+ message: 'Typo in prop type chain qualifier: isrequired'
+ }, {
+ message: 'Typo in prop type chain qualifier: isrequired'
}]
- // ---------
- // createClass tests above fail, so they're commented out
- */
+ }, {
+ code: `
+ import React from 'react';
+ import PropTypes from 'prop-types';
+ const Component = React.createReactClass({
+ childContextTypes: {
+ a: PropTypes.bools,
+ b: PropTypes.Array,
+ c: PropTypes.function,
+ d: PropTypes.objectof,
+ }
+ });
+ `,
+ parser: 'babel-eslint',
+ parserOptions: parserOptions,
+ errors: [{
+ message: 'Typo in declared prop type: bools'
+ }, {
+ message: 'Typo in declared prop type: Array'
+ }, {
+ message: 'Typo in declared prop type: function'
+ }, {
+ message: 'Typo in declared prop type: objectof'
+ }]
+ }, {
+ code: `
+ import React from 'react';
+ import PropTypes from 'prop-types';
+ const Component = React.createReactClass({
+ propTypes: {
+ a: PropTypes.string.isrequired,
+ b: PropTypes.shape({
+ c: PropTypes.number
+ }).isrequired
+ }
+ });
+ `,
+ parserOptions: parserOptions,
+ errors: [{
+ message: 'Typo in prop type chain qualifier: isrequired'
+ }, {
+ message: 'Typo in prop type chain qualifier: isrequired'
+ }]
+ }, {
+ code: `
+ import React from 'react';
+ import PropTypes from 'prop-types';
+ const Component = React.createReactClass({
+ childContextTypes: {
+ a: PropTypes.bools,
+ b: PropTypes.Array,
+ c: PropTypes.function,
+ d: PropTypes.objectof,
+ }
+ });
+ `,
+ parserOptions: parserOptions,
+ errors: [{
+ message: 'Typo in declared prop type: bools'
+ }, {
+ message: 'Typo in declared prop type: Array'
+ }, {
+ message: 'Typo in declared prop type: function'
+ }, {
+ message: 'Typo in declared prop type: objectof'
+ }]
+ }, {
+ code: `
+ import React from 'react';
+ const Component = React.createReactClass({
+ proptypes: {},
+ childcontexttypes: {},
+ contexttypes: {},
+ ComponentWillMount() { },
+ ComponentDidMount() { },
+ ComponentWillReceiveProps() { },
+ ShouldComponentUpdate() { },
+ ComponentWillUpdate() { },
+ ComponentDidUpdate() { },
+ ComponentWillUnmount() { },
+ render() {
+ return Hello {this.props.name}
;
+ }
+ });
+ `,
+ parserOptions: parserOptions,
+ errors: [{
+ message: ERROR_MESSAGE_ES5,
+ type: 'ObjectExpression'
+ }, {
+ message: ERROR_MESSAGE_ES5,
+ type: 'ObjectExpression'
+ }, {
+ message: ERROR_MESSAGE_ES5,
+ type: 'ObjectExpression'
+ }, {
+ message: ERROR_MESSAGE_LIFECYCLE_METHOD,
+ type: 'Property'
+ }, {
+ message: ERROR_MESSAGE_LIFECYCLE_METHOD,
+ type: 'Property'
+ }, {
+ message: ERROR_MESSAGE_LIFECYCLE_METHOD,
+ type: 'Property'
+ }, {
+ message: ERROR_MESSAGE_LIFECYCLE_METHOD,
+ type: 'Property'
+ }, {
+ message: ERROR_MESSAGE_LIFECYCLE_METHOD,
+ type: 'Property'
+ }, {
+ message: ERROR_MESSAGE_LIFECYCLE_METHOD,
+ type: 'Property'
+ }, {
+ message: ERROR_MESSAGE_LIFECYCLE_METHOD,
+ type: 'Property'
+ }]
+ }, {
+ code: `
+ import React from 'react';
+ const Component = React.createReactClass({
+ proptypes: {},
+ childcontexttypes: {},
+ contexttypes: {},
+ ComponentWillMount() { },
+ ComponentDidMount() { },
+ ComponentWillReceiveProps() { },
+ ShouldComponentUpdate() { },
+ ComponentWillUpdate() { },
+ ComponentDidUpdate() { },
+ ComponentWillUnmount() { },
+ render() {
+ return Hello {this.props.name}
;
+ }
+ });
+ `,
+ parser: 'babel-eslint',
+ parserOptions: parserOptions,
+ errors: [{
+ message: ERROR_MESSAGE_ES5,
+ type: 'ObjectExpression'
+ }, {
+ message: ERROR_MESSAGE_ES5,
+ type: 'ObjectExpression'
+ }, {
+ message: ERROR_MESSAGE_ES5,
+ type: 'ObjectExpression'
+ }, {
+ message: ERROR_MESSAGE_LIFECYCLE_METHOD,
+ type: 'Property'
+ }, {
+ message: ERROR_MESSAGE_LIFECYCLE_METHOD,
+ type: 'Property'
+ }, {
+ message: ERROR_MESSAGE_LIFECYCLE_METHOD,
+ type: 'Property'
+ }, {
+ message: ERROR_MESSAGE_LIFECYCLE_METHOD,
+ type: 'Property'
+ }, {
+ message: ERROR_MESSAGE_LIFECYCLE_METHOD,
+ type: 'Property'
+ }, {
+ message: ERROR_MESSAGE_LIFECYCLE_METHOD,
+ type: 'Property'
+ }, {
+ message: ERROR_MESSAGE_LIFECYCLE_METHOD,
+ type: 'Property'
+ }]
+ /*
+ // PropTypes declared on a component that is detected through JSDoc comments and is
+ // declared AFTER the PropTypes assignment
+ // Commented out since it only works with ESLint 5.
+ ,{
+ code: `
+ MyComponent.PROPTYPES = {}
+ \/** @extends React.Component *\/
+ class MyComponent extends BaseComponent {}
+ `,
+ parserOptions: parserOptions
+ },
+ */
+ }]
});