Skip to content

Commit

Permalink
Add common use-case tests and fix bugs
Browse files Browse the repository at this point in the history
Added tests for:
- `import { PropTypes } from 'react';`
- `React.PropTypes`o

Fixed a couple subtle bugs that made these new tests fail, and made a
small refactoring that made this code easier to understand and fix.
  • Loading branch information
brettdh authored and ljharb committed Feb 9, 2018
1 parent d318e52 commit 352642f
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 33 deletions.
76 changes: 43 additions & 33 deletions lib/rules/no-typos.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ module.exports = {
},

create: Components.detect((context, components, utils) => {
let propTypesPackageName;
let reactPackageName;
let propTypesPackageName = null;
let reactPackageName = null;

function checkValidPropTypeQualfier(node) {
if (node.name !== 'isRequired') {
Expand All @@ -57,43 +57,53 @@ module.exports = {
}
}

function isPropTypesPackage(node) {
return (
node.type === 'Identifier' &&
node.name == propTypesPackageName
) || (
node.type === 'MemberExpression' &&
node.property.name === 'PropTypes' &&
node.object.name == reactPackageName
);
}

function checkValidCallExpression(node) {
const callee = node.callee;
if (callee.type === 'MemberExpression' && callee.property.name === 'shape') {
checkValidPropObject(node.arguments[0]);
} else if (callee.type === 'MemberExpression' && callee.property.name === 'oneOfType') {
const args = node.arguments[0];
if (args && args.type === 'ArrayExpression') {
args.elements.forEach(el => checkValidProp(el));
}
}
}

/* eslint-disable no-use-before-define */
function checkValidProp(node) {
if ((!propTypesPackageName && !reactPackageName) || !node) {
return;
}

if (
node.type === 'MemberExpression' &&
node.object.type === 'MemberExpression' &&
node.object.object.name === propTypesPackageName
) { // PropTypes.myProp.isRequired
checkValidPropType(node.object.property);
checkValidPropTypeQualfier(node.property);
} else if (
node.type === 'MemberExpression' &&
node.object.type === 'Identifier' &&
node.object.name === propTypesPackageName &&
node.property.name !== 'isRequired'
) { // PropTypes.myProp
checkValidPropType(node.property);
} else if (
(node.type === 'MemberExpression' && node.object.type === 'CallExpression') ||
node.type === 'CallExpression'
) { // Shapes
if (node.type === 'MemberExpression') {
if (node.type === 'MemberExpression') {
if (
node.object.type === 'MemberExpression' &&
isPropTypesPackage(node.object.object)
) { // PropTypes.myProp.isRequired
checkValidPropType(node.object.property);
checkValidPropTypeQualfier(node.property);
node = node.object;
}
const callee = node.callee;
if (callee.type === 'MemberExpression' && callee.property.name === 'shape') {
checkValidPropObject(node.arguments[0]);
} else if (callee.type === 'MemberExpression' && callee.property.name === 'oneOfType') {
const args = node.arguments[0];
if (args && args.type === 'ArrayExpression') {
args.elements.forEach(el => checkValidProp(el));
}
} else if (
isPropTypesPackage(node.object) &&
node.property.name !== 'isRequired'
) { // PropTypes.myProp
checkValidPropType(node.property);
} else if (node.object.type === 'CallExpression') {
checkValidPropTypeQualfier(node.property);
checkValidCallExpression(node.object);
}
} else if (node.type === 'CallExpression') {
checkValidCallExpression(node);
}
}

Expand Down Expand Up @@ -136,11 +146,11 @@ module.exports = {
} else if (node.source && node.source.value === 'react') { // import { PropTypes } from "react"
reactPackageName = node.specifiers[0].local.name;

propTypesPackageName = node.specifiers.length > 1 && (
propTypesPackageName = node.specifiers.length >= 1 ? (
node.specifiers
.filter(specifier => specifier.imported && specifier.imported.name === 'PropTypes')
.map(specifier => specifier.local.name)
);
) : null;
}
},

Expand Down
80 changes: 80 additions & 0 deletions tests/lib/rules/no-typos.js
Original file line number Diff line number Diff line change
Expand Up @@ -980,5 +980,85 @@ ruleTester.run('no-typos', rule, {
}, {
message: 'Typo in declared prop type: objectof'
}]
}, {
code: `
import React from 'react';
class Component extends React.Component {};
Component.propTypes = {
a: React.PropTypes.string.isrequired,
b: React.PropTypes.shape({
c: React.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';
class Component extends React.Component {};
Component.childContextTypes = {
a: React.PropTypes.bools,
b: React.PropTypes.Array,
c: React.PropTypes.function,
d: React.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 { PropTypes } from 'react';
class Component extends React.Component {};
Component.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 { PropTypes } from 'react';
class Component extends React.Component {};
Component.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'
}]
}]
});

0 comments on commit 352642f

Please sign in to comment.