Skip to content

Commit 352642f

Browse files
brettdhljharb
authored andcommitted
Add common use-case tests and fix bugs
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.
1 parent d318e52 commit 352642f

File tree

2 files changed

+123
-33
lines changed

2 files changed

+123
-33
lines changed

lib/rules/no-typos.js

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ module.exports = {
3636
},
3737

3838
create: Components.detect((context, components, utils) => {
39-
let propTypesPackageName;
40-
let reactPackageName;
39+
let propTypesPackageName = null;
40+
let reactPackageName = null;
4141

4242
function checkValidPropTypeQualfier(node) {
4343
if (node.name !== 'isRequired') {
@@ -57,43 +57,53 @@ module.exports = {
5757
}
5858
}
5959

60+
function isPropTypesPackage(node) {
61+
return (
62+
node.type === 'Identifier' &&
63+
node.name == propTypesPackageName
64+
) || (
65+
node.type === 'MemberExpression' &&
66+
node.property.name === 'PropTypes' &&
67+
node.object.name == reactPackageName
68+
);
69+
}
70+
71+
function checkValidCallExpression(node) {
72+
const callee = node.callee;
73+
if (callee.type === 'MemberExpression' && callee.property.name === 'shape') {
74+
checkValidPropObject(node.arguments[0]);
75+
} else if (callee.type === 'MemberExpression' && callee.property.name === 'oneOfType') {
76+
const args = node.arguments[0];
77+
if (args && args.type === 'ArrayExpression') {
78+
args.elements.forEach(el => checkValidProp(el));
79+
}
80+
}
81+
}
82+
6083
/* eslint-disable no-use-before-define */
6184
function checkValidProp(node) {
6285
if ((!propTypesPackageName && !reactPackageName) || !node) {
6386
return;
6487
}
6588

66-
if (
67-
node.type === 'MemberExpression' &&
68-
node.object.type === 'MemberExpression' &&
69-
node.object.object.name === propTypesPackageName
70-
) { // PropTypes.myProp.isRequired
71-
checkValidPropType(node.object.property);
72-
checkValidPropTypeQualfier(node.property);
73-
} else if (
74-
node.type === 'MemberExpression' &&
75-
node.object.type === 'Identifier' &&
76-
node.object.name === propTypesPackageName &&
77-
node.property.name !== 'isRequired'
78-
) { // PropTypes.myProp
79-
checkValidPropType(node.property);
80-
} else if (
81-
(node.type === 'MemberExpression' && node.object.type === 'CallExpression') ||
82-
node.type === 'CallExpression'
83-
) { // Shapes
84-
if (node.type === 'MemberExpression') {
89+
if (node.type === 'MemberExpression') {
90+
if (
91+
node.object.type === 'MemberExpression' &&
92+
isPropTypesPackage(node.object.object)
93+
) { // PropTypes.myProp.isRequired
94+
checkValidPropType(node.object.property);
8595
checkValidPropTypeQualfier(node.property);
86-
node = node.object;
87-
}
88-
const callee = node.callee;
89-
if (callee.type === 'MemberExpression' && callee.property.name === 'shape') {
90-
checkValidPropObject(node.arguments[0]);
91-
} else if (callee.type === 'MemberExpression' && callee.property.name === 'oneOfType') {
92-
const args = node.arguments[0];
93-
if (args && args.type === 'ArrayExpression') {
94-
args.elements.forEach(el => checkValidProp(el));
95-
}
96+
} else if (
97+
isPropTypesPackage(node.object) &&
98+
node.property.name !== 'isRequired'
99+
) { // PropTypes.myProp
100+
checkValidPropType(node.property);
101+
} else if (node.object.type === 'CallExpression') {
102+
checkValidPropTypeQualfier(node.property);
103+
checkValidCallExpression(node.object);
96104
}
105+
} else if (node.type === 'CallExpression') {
106+
checkValidCallExpression(node);
97107
}
98108
}
99109

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

139-
propTypesPackageName = node.specifiers.length > 1 && (
149+
propTypesPackageName = node.specifiers.length >= 1 ? (
140150
node.specifiers
141151
.filter(specifier => specifier.imported && specifier.imported.name === 'PropTypes')
142152
.map(specifier => specifier.local.name)
143-
);
153+
) : null;
144154
}
145155
},
146156

tests/lib/rules/no-typos.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,5 +980,85 @@ ruleTester.run('no-typos', rule, {
980980
}, {
981981
message: 'Typo in declared prop type: objectof'
982982
}]
983+
}, {
984+
code: `
985+
import React from 'react';
986+
class Component extends React.Component {};
987+
Component.propTypes = {
988+
a: React.PropTypes.string.isrequired,
989+
b: React.PropTypes.shape({
990+
c: React.PropTypes.number
991+
}).isrequired
992+
}
993+
`,
994+
parser: 'babel-eslint',
995+
parserOptions: parserOptions,
996+
errors: [{
997+
message: 'Typo in prop type chain qualifier: isrequired'
998+
}, {
999+
message: 'Typo in prop type chain qualifier: isrequired'
1000+
}]
1001+
}, {
1002+
code: `
1003+
import React from 'react';
1004+
class Component extends React.Component {};
1005+
Component.childContextTypes = {
1006+
a: React.PropTypes.bools,
1007+
b: React.PropTypes.Array,
1008+
c: React.PropTypes.function,
1009+
d: React.PropTypes.objectof,
1010+
}
1011+
`,
1012+
parser: 'babel-eslint',
1013+
parserOptions: parserOptions,
1014+
errors: [{
1015+
message: 'Typo in declared prop type: bools'
1016+
}, {
1017+
message: 'Typo in declared prop type: Array'
1018+
}, {
1019+
message: 'Typo in declared prop type: function'
1020+
}, {
1021+
message: 'Typo in declared prop type: objectof'
1022+
}]
1023+
}, {
1024+
code: `
1025+
import { PropTypes } from 'react';
1026+
class Component extends React.Component {};
1027+
Component.propTypes = {
1028+
a: PropTypes.string.isrequired,
1029+
b: PropTypes.shape({
1030+
c: PropTypes.number
1031+
}).isrequired
1032+
}
1033+
`,
1034+
parser: 'babel-eslint',
1035+
parserOptions: parserOptions,
1036+
errors: [{
1037+
message: 'Typo in prop type chain qualifier: isrequired'
1038+
}, {
1039+
message: 'Typo in prop type chain qualifier: isrequired'
1040+
}]
1041+
}, {
1042+
code: `
1043+
import { PropTypes } from 'react';
1044+
class Component extends React.Component {};
1045+
Component.childContextTypes = {
1046+
a: PropTypes.bools,
1047+
b: PropTypes.Array,
1048+
c: PropTypes.function,
1049+
d: PropTypes.objectof,
1050+
}
1051+
`,
1052+
parser: 'babel-eslint',
1053+
parserOptions: parserOptions,
1054+
errors: [{
1055+
message: 'Typo in declared prop type: bools'
1056+
}, {
1057+
message: 'Typo in declared prop type: Array'
1058+
}, {
1059+
message: 'Typo in declared prop type: function'
1060+
}, {
1061+
message: 'Typo in declared prop type: objectof'
1062+
}]
9831063
}]
9841064
});

0 commit comments

Comments
 (0)