Skip to content

Commit

Permalink
Better warnings for nested propTypes
Browse files Browse the repository at this point in the history
`arrayOf`, `shape` and `objectOf` warnings now display the full path of
the invalid key.
  • Loading branch information
alexkirsz committed May 14, 2015
1 parent 05b98ac commit 12a43d4
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 39 deletions.
12 changes: 6 additions & 6 deletions src/addons/link/__tests__/ReactLinkPropTypes-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,22 @@ describe('ReactLink', function() {
typeCheckFail(
LinkPropTypes.link(React.PropTypes.any),
{},
'Required prop `value` was not specified in `testComponent`.'
'Required prop `testProp.value` was not specified in `testComponent`.'
);
typeCheckFail(
LinkPropTypes.link(React.PropTypes.any),
{value: 123},
'Required prop `requestChange` was not specified in `testComponent`.'
'Required prop `testProp.requestChange` was not specified in `testComponent`.'
);
typeCheckFail(
LinkPropTypes.link(React.PropTypes.any),
{requestChange: emptyFunction},
'Required prop `value` was not specified in `testComponent`.'
'Required prop `testProp.value` was not specified in `testComponent`.'
);
typeCheckFail(
LinkPropTypes.link(React.PropTypes.any),
{value: null, requestChange: null},
'Required prop `value` was not specified in `testComponent`.'
'Required prop `testProp.value` was not specified in `testComponent`.'
);
});

Expand Down Expand Up @@ -104,7 +104,7 @@ describe('ReactLink', function() {
typeCheckFail(
LinkPropTypes.link(React.PropTypes.string),
{value: 123, requestChange: emptyFunction},
'Invalid prop `value` of type `number` supplied to `testComponent`,' +
'Invalid prop `testProp.value` of type `number` supplied to `testComponent`,' +
' expected `string`.'
);
});
Expand Down Expand Up @@ -148,7 +148,7 @@ describe('ReactLink', function() {
typeCheckFail(
LinkPropTypes.link(React.PropTypes.oneOfType([React.PropTypes.number])),
{value: 'imastring', requestChange: emptyFunction},
'Invalid prop `value` supplied to `testComponent`.'
'Invalid prop `testProp.value` supplied to `testComponent`.'
);
});
});
81 changes: 55 additions & 26 deletions src/classic/types/ReactPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,27 @@ var ReactPropTypes = {
};

function createChainableTypeChecker(validate) {
function checkType(isRequired, props, propName, componentName, location) {
function checkType(
isRequired,
props,
propName,
componentName,
location,
propFullName
) {
componentName = componentName || ANONYMOUS;
propFullName = propFullName || propName;
if (props[propName] == null) {
var locationName = ReactPropTypeLocationNames[location];
if (isRequired) {
return new Error(
`Required ${locationName} \`${propName}\` was not specified in ` +
`Required ${locationName} \`${propFullName}\` was not specified in ` +
`\`${componentName}\`.`
);
}
return null;
} else {
return validate(props, propName, componentName, location);
return validate(props, propName, componentName, location, propFullName);
}
}

Expand All @@ -109,7 +117,7 @@ function createChainableTypeChecker(validate) {
}

function createPrimitiveTypeChecker(expectedType) {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
var propValue = props[propName];
var propType = getPropType(propValue);
if (propType !== expectedType) {
Expand All @@ -120,8 +128,9 @@ function createPrimitiveTypeChecker(expectedType) {
var preciseType = getPreciseType(propValue);

return new Error(
`Invalid ${locationName} \`${propName}\` of type \`${preciseType}\` ` +
`supplied to \`${componentName}\`, expected \`${expectedType}\`.`
`Invalid ${locationName} \`${propFullName}\` of type ` +
`\`${preciseType}\` supplied to \`${componentName}\`, expected ` +
`\`${expectedType}\`.`
);
}
return null;
Expand All @@ -134,18 +143,24 @@ function createAnyTypeChecker() {
}

function createArrayOfTypeChecker(typeChecker) {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
var propValue = props[propName];
if (!Array.isArray(propValue)) {
var locationName = ReactPropTypeLocationNames[location];
var propType = getPropType(propValue);
return new Error(
`Invalid ${locationName} \`${propName}\` of type ` +
`Invalid ${locationName} \`${propFullName}\` of type ` +
`\`${propType}\` supplied to \`${componentName}\`, expected an array.`
);
}
for (var i = 0; i < propValue.length; i++) {
var error = typeChecker(propValue, i, componentName, location);
var error = typeChecker(
propValue,
i,
componentName,
location,
`${propFullName}[${i}]`
);
if (error instanceof Error) {
return error;
}
Expand All @@ -156,11 +171,11 @@ function createArrayOfTypeChecker(typeChecker) {
}

function createElementTypeChecker() {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
if (!ReactElement.isValidElement(props[propName])) {
var locationName = ReactPropTypeLocationNames[location];
return new Error(
`Invalid ${locationName} \`${propName}\` supplied to ` +
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
`\`${componentName}\`, expected a single ReactElement.`
);
}
Expand All @@ -170,12 +185,12 @@ function createElementTypeChecker() {
}

function createInstanceTypeChecker(expectedClass) {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
if (!(props[propName] instanceof expectedClass)) {
var locationName = ReactPropTypeLocationNames[location];
var expectedClassName = expectedClass.name || ANONYMOUS;
return new Error(
`Invalid ${locationName} \`${propName}\` supplied to ` +
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
`\`${componentName}\`, expected instance of \`${expectedClassName}\`.`
);
}
Expand All @@ -185,7 +200,7 @@ function createInstanceTypeChecker(expectedClass) {
}

function createEnumTypeChecker(expectedValues) {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
var propValue = props[propName];
for (var i = 0; i < expectedValues.length; i++) {
if (propValue === expectedValues[i]) {
Expand All @@ -196,27 +211,33 @@ function createEnumTypeChecker(expectedValues) {
var locationName = ReactPropTypeLocationNames[location];
var valuesString = JSON.stringify(expectedValues);
return new Error(
`Invalid ${locationName} \`${propName}\` of value \`${propValue}\` ` +
`Invalid ${locationName} \`${propFullName}\` of value \`${propValue}\` ` +
`supplied to \`${componentName}\`, expected one of ${valuesString}.`
);
}
return createChainableTypeChecker(validate);
}

function createObjectOfTypeChecker(typeChecker) {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
var propValue = props[propName];
var propType = getPropType(propValue);
if (propType !== 'object') {
var locationName = ReactPropTypeLocationNames[location];
return new Error(
`Invalid ${locationName} \`${propName}\` of type ` +
`Invalid ${locationName} \`${propFullName}\` of type ` +
`\`${propType}\` supplied to \`${componentName}\`, expected an object.`
);
}
for (var key in propValue) {
if (propValue.hasOwnProperty(key)) {
var error = typeChecker(propValue, key, componentName, location);
var error = typeChecker(
propValue,
key,
componentName,
location,
`${propFullName}.${key}`
);
if (error instanceof Error) {
return error;
}
Expand All @@ -228,29 +249,31 @@ function createObjectOfTypeChecker(typeChecker) {
}

function createUnionTypeChecker(arrayOfTypeCheckers) {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
for (var i = 0; i < arrayOfTypeCheckers.length; i++) {
var checker = arrayOfTypeCheckers[i];
if (checker(props, propName, componentName, location) == null) {
if (
checker(props, propName, componentName, location, propFullName) == null
) {
return null;
}
}

var locationName = ReactPropTypeLocationNames[location];
return new Error(
`Invalid ${locationName} \`${propName}\` supplied to ` +
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
`\`${componentName}\`.`
);
}
return createChainableTypeChecker(validate);
}

function createNodeChecker() {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
if (!isNode(props[propName])) {
var locationName = ReactPropTypeLocationNames[location];
return new Error(
`Invalid ${locationName} \`${propName}\` supplied to ` +
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
`\`${componentName}\`, expected a ReactNode.`
);
}
Expand All @@ -260,13 +283,13 @@ function createNodeChecker() {
}

function createShapeTypeChecker(shapeTypes) {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
var propValue = props[propName];
var propType = getPropType(propValue);
if (propType !== 'object') {
var locationName = ReactPropTypeLocationNames[location];
return new Error(
`Invalid ${locationName} \`${propName}\` of type \`${propType}\` ` +
`Invalid ${locationName} \`${propFullName}\` of type \`${propType}\` ` +
`supplied to \`${componentName}\`, expected \`object\`.`
);
}
Expand All @@ -275,7 +298,13 @@ function createShapeTypeChecker(shapeTypes) {
if (!checker) {
continue;
}
var error = checker(propValue, key, componentName, location);
var error = checker(
propValue,
key,
componentName,
location,
`${propFullName}.${key}`
);
if (error) {
return error;
}
Expand Down
14 changes: 7 additions & 7 deletions src/classic/types/__tests__/ReactPropTypes-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ describe('ReactPropTypes', function() {
typeCheckFail(
PropTypes.arrayOf(PropTypes.number),
[1, 2, 'b'],
'Invalid prop `2` of type `string` supplied to `testComponent`, ' +
'Invalid prop `testProp[2]` of type `string` supplied to `testComponent`, ' +
'expected `number`.'
);
});
Expand All @@ -173,7 +173,7 @@ describe('ReactPropTypes', function() {
typeCheckFail(
PropTypes.arrayOf(PropTypes.instanceOf(Thing)),
[new Thing(), 'xyz'],
'Invalid prop `1` supplied to `testComponent`, expected instance of `' +
'Invalid prop `testProp[1]` supplied to `testComponent`, expected instance of `' +
name + '`.'
);
});
Expand Down Expand Up @@ -458,7 +458,7 @@ describe('ReactPropTypes', function() {
typeCheckFail(
PropTypes.objectOf(PropTypes.number),
{a: 1, b: 2, c: 'b'},
'Invalid prop `c` of type `string` supplied to `testComponent`, ' +
'Invalid prop `testProp.c` of type `string` supplied to `testComponent`, ' +
'expected `number`.'
);
});
Expand All @@ -470,7 +470,7 @@ describe('ReactPropTypes', function() {
typeCheckFail(
PropTypes.objectOf(PropTypes.instanceOf(Thing)),
{a: new Thing(), b: 'xyz'},
'Invalid prop `b` supplied to `testComponent`, expected instance of `' +
'Invalid prop `testProp.b` supplied to `testComponent`, expected instance of `' +
name + '`.'
);
});
Expand Down Expand Up @@ -668,7 +668,7 @@ describe('ReactPropTypes', function() {
typeCheckFail(
PropTypes.shape({key: PropTypes.number.isRequired}),
{},
'Required prop `key` was not specified in `testComponent`.'
'Required prop `testProp.key` was not specified in `testComponent`.'
);
});

Expand All @@ -679,14 +679,14 @@ describe('ReactPropTypes', function() {
secondKey: PropTypes.number.isRequired
}),
{},
'Required prop `key` was not specified in `testComponent`.'
'Required prop `testProp.key` was not specified in `testComponent`.'
);
});

it("should warn for invalid key types", function() {
typeCheckFail(PropTypes.shape({key: PropTypes.number}),
{key: 'abc'},
'Invalid prop `key` of type `string` supplied to `testComponent`, ' +
'Invalid prop `testProp.key` of type `string` supplied to `testComponent`, ' +
'expected `number`.'
);
});
Expand Down

0 comments on commit 12a43d4

Please sign in to comment.