From 6520b2b2446268596eb5a00393bb593beb22face Mon Sep 17 00:00:00 2001 From: Jonas Berlin Date: Fri, 24 Feb 2017 16:25:49 +0200 Subject: [PATCH 01/11] Support & validate arguments to complex propTypes --- lib/rules/jsx-no-invalid-props.js | 172 +++++++++++++++++++++++------- 1 file changed, 131 insertions(+), 41 deletions(-) diff --git a/lib/rules/jsx-no-invalid-props.js b/lib/rules/jsx-no-invalid-props.js index 50f3268..359edfc 100644 --- a/lib/rules/jsx-no-invalid-props.js +++ b/lib/rules/jsx-no-invalid-props.js @@ -12,6 +12,15 @@ const utils = require('../utilities'); //------------------------------------------------------------------------------ module.exports = (context) => { + /* + 1 = proptype + 2 = invalid (bad spelling) + 3 = function(proptype | function) + 4 = function(jstype) + 5 = function(array-with-strings) + 6 = function(array-with-proptypes) + 7 = function(object-with-proptypes) + */ const validProps = { array: 1, bool: 1, @@ -22,59 +31,140 @@ module.exports = (context) => { object: 1, obj: 2, string: 1, + symbol: 1, any: 1, - arrayOf: 1, + arrayOf: 3, element: 1, - instanceOf: 1, + instanceOf: 4, node: 1, - objectOf: 1, - oneOf: 1, - oneOfType: 1, - shape: 1, + objectOf: 3, + oneOf: 5, + oneOfType: 6, + shape: 7, }; - function isExpectedReactPropSyntax(declaration) { - return declaration.value.object && (( - declaration.value.object.name === 'PropTypes' - ) - || ( - declaration.value.object.object.name === 'React' - && declaration.value.object.property.name === 'PropTypes' - ) - || ( - declaration.value.object.object.name === 'PropTypes' - && declaration.value.property.name === 'isRequired' - ) - || ( - declaration.value.object.object.object.name === 'React' - && declaration.value.object.object.property.name === 'PropTypes' - && declaration.value.property.name === 'isRequired' - )); - } - function getPropValue(propName) { return validProps[propName]; } - function checkProperties(declarations) { - declarations.forEach((declaration) => { - if (isExpectedReactPropSyntax(declaration)) { - let propName = ''; - let propValue = 0; - if (declaration.value.property.name === 'isRequired') { - propName = declaration.value.object.property.name; - } else { - propName = declaration.value.property.name; - } + var argType = { + 3: "proptype", + 4: "JS type", + 5: "array with strings", + 6: "array with proptypes", + 7: "object with proptypes as values", + } - propValue = getPropValue(propName); + function isIdentifier(n, ident) { + return n.type === 'Identifier' && (ident === undefined || n.name === ident); + } - if (propValue !== 1) { - context.report(declaration, `${propName} is not a valid PropType`); - } - } else { - context.report(declaration, 'unknown use of PropTypes'); + function isIdentifierOrRawLiteral(n) { + if (n.type === 'Identifier') { + return n.name; + } + if (n.type === 'Literal') { + return n.raw; + } + return ""; + } + + function isPropTypesExpression(n) { + if (n.type !== 'MemberExpression') return false; + if (isIdentifier(n.object, 'PropTypes')) return true; + return n.object.type === 'MemberExpression' && isIdentifier(n.object.object, 'React') && isIdentifier(n.object.property, 'PropTypes'); + } + + function isArray(n) { + return n.type === 'ArrayExpression'; + } + + function isObject(n) { + return n.type === 'ObjectExpression'; + } + + function assertExpectedReactPropSyntax(key, declaration, allowIsRequired) { + if (declaration.type === 'MemberExpression' && + isIdentifier(declaration.property, "isRequired")) { + if (!allowIsRequired) { + return context.report(declaration, key + ": please move isRequired qualifier outside the oneOfType() declaration e.g. '(React.)PropTypes.oneOfType(...).isRequired'"); + } + declaration = declaration.object; + } + if (declaration.type === 'CallExpression') { + if (!isPropTypesExpression(declaration.callee) || !isIdentifier(declaration.callee.property)) { + return context.report(declaration, key + ": unsupported proptypes call syntax"); + } + var propType = declaration.callee.property.name; + var propValue = getPropValue(propType); + switch (propValue) { + case 3: case 4: case 5: case 6: case 7: break; + default: return context.report(declaration, key + ': ' + propType + ' must be called like a function'); + } + if (declaration.arguments.length !== 1) { + context.report(declaration, key + ': ' + propType + ' expects exactly one argument of type ' + argType[propValue]); + } + var arg = declaration.arguments[0]; + if (((propValue == 3 || propValue == 4) && isArray(arg) || isObject(arg)) + || ((propValue == 5 || propValue == 6) && !isArray(arg)) + || (propValue == 7 && !isObject(arg))) { + return context.report(declaration, key + ': ' + propType + ' expects exactly one argument of type ' + argType[propValue]); } + switch (propValue) { + case 3: + assertExpectedReactPropSyntax(key, arg, true); + break; + case 4: + if (arg.type === 'Literal') { + context.report(declaration, key + ': ' + propType + ' argument must not be a literal'); + } + break; + case 5: + arg.elements.every(function (elem) { + if (elem.type !== 'Literal') { + context.report(declaration, key + ': ' + propType + ' array entries must be literals'); + return false; + } + return true; + }); + break; + case 6: + arg.elements.forEach(function (elem) { + return assertExpectedReactPropSyntax(key, elem, false); + }); + break; + case 7: + arg.properties.every(function (elem) { + if (elem.type !== 'Property') { + context.report(declaration, key + ': ' + propType + ' object must only consist of properties'); + return false; + } + if (!isIdentifierOrRawLiteral(elem.key)) { + context.report(declaration, key + ': ' + propType + ' properties must be identifiers or literals'); + } + assertExpectedReactPropSyntax(key, elem.value, true); + return true; + }); + break; + } + } else if (isPropTypesExpression(declaration) && isIdentifier(declaration.property)) { + var propType = declaration.property.name; + var propValue = getPropValue(propType); + switch (propValue) { + case 1: return; + case 2: return context.report(declaration, key + ': ' + propType + ' is a misspelled prop type. Please correct spelling'); + default: return context.report(declaration, key + ': ' + propType + ' is not a known prop type'); + } + } else if (declaration.type === 'FunctionExpression') { + // ok + } else { + context.report(declaration, key + ': unknown use of PropTypes'); + } + } + + function checkProperties(declarations) { + declarations.forEach((declaration) => { + assertExpectedReactPropSyntax(isIdentifierOrRawLiteral(declaration), declaration.value, true); }); } From 4ceaaf520bfbc3d224e11a3e60910da58b63d180 Mon Sep 17 00:00:00 2001 From: Jonas Berlin Date: Fri, 24 Feb 2017 16:48:06 +0200 Subject: [PATCH 02/11] Fix reporting of offending key --- lib/rules/jsx-no-invalid-props.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/jsx-no-invalid-props.js b/lib/rules/jsx-no-invalid-props.js index 359edfc..30fda51 100644 --- a/lib/rules/jsx-no-invalid-props.js +++ b/lib/rules/jsx-no-invalid-props.js @@ -164,7 +164,7 @@ module.exports = (context) => { function checkProperties(declarations) { declarations.forEach((declaration) => { - assertExpectedReactPropSyntax(isIdentifierOrRawLiteral(declaration), declaration.value, true); + assertExpectedReactPropSyntax(isIdentifierOrRawLiteral(declaration.key), declaration.value, true); }); } From b426fc2a880d4c5ea9e1a6be846113fd4afcec32 Mon Sep 17 00:00:00 2001 From: Jonas Berlin Date: Fri, 24 Feb 2017 16:48:38 +0200 Subject: [PATCH 03/11] Improve reporting readability --- lib/rules/jsx-no-invalid-props.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/rules/jsx-no-invalid-props.js b/lib/rules/jsx-no-invalid-props.js index 30fda51..08b381a 100644 --- a/lib/rules/jsx-no-invalid-props.js +++ b/lib/rules/jsx-no-invalid-props.js @@ -87,28 +87,28 @@ module.exports = (context) => { if (declaration.type === 'MemberExpression' && isIdentifier(declaration.property, "isRequired")) { if (!allowIsRequired) { - return context.report(declaration, key + ": please move isRequired qualifier outside the oneOfType() declaration e.g. '(React.)PropTypes.oneOfType(...).isRequired'"); + return context.report(declaration, "property '" + key + "': please move isRequired qualifier outside the oneOfType() declaration e.g. '(React.)PropTypes.oneOfType(...).isRequired'"); } declaration = declaration.object; } if (declaration.type === 'CallExpression') { if (!isPropTypesExpression(declaration.callee) || !isIdentifier(declaration.callee.property)) { - return context.report(declaration, key + ": unsupported proptypes call syntax"); + return context.report(declaration, "property '" + key + "': unsupported proptypes call syntax"); } var propType = declaration.callee.property.name; var propValue = getPropValue(propType); switch (propValue) { case 3: case 4: case 5: case 6: case 7: break; - default: return context.report(declaration, key + ': ' + propType + ' must be called like a function'); + default: return context.report(declaration, "property '" + key + "': " + propType + ' must be called like a function'); } if (declaration.arguments.length !== 1) { - context.report(declaration, key + ': ' + propType + ' expects exactly one argument of type ' + argType[propValue]); + context.report(declaration, "property '" + key + "': " + propType + ' expects exactly one argument of type ' + argType[propValue]); } var arg = declaration.arguments[0]; if (((propValue == 3 || propValue == 4) && isArray(arg) || isObject(arg)) || ((propValue == 5 || propValue == 6) && !isArray(arg)) || (propValue == 7 && !isObject(arg))) { - return context.report(declaration, key + ': ' + propType + ' expects exactly one argument of type ' + argType[propValue]); + return context.report(declaration, "property '" + key + "': " + propType + ' expects exactly one argument of type ' + argType[propValue]); } switch (propValue) { case 3: @@ -116,13 +116,13 @@ module.exports = (context) => { break; case 4: if (arg.type === 'Literal') { - context.report(declaration, key + ': ' + propType + ' argument must not be a literal'); + context.report(declaration, "property '" + key + "': " + propType + ' argument must not be a literal'); } break; case 5: arg.elements.every(function (elem) { if (elem.type !== 'Literal') { - context.report(declaration, key + ': ' + propType + ' array entries must be literals'); + context.report(declaration, "property '" + key + "': " + propType + ' array entries must be literals'); return false; } return true; @@ -136,11 +136,11 @@ module.exports = (context) => { case 7: arg.properties.every(function (elem) { if (elem.type !== 'Property') { - context.report(declaration, key + ': ' + propType + ' object must only consist of properties'); + context.report(declaration, "property '" + key + "': " + propType + ' object must only consist of properties'); return false; } if (!isIdentifierOrRawLiteral(elem.key)) { - context.report(declaration, key + ': ' + propType + ' properties must be identifiers or literals'); + context.report(declaration, "property '" + key + "': " + propType + ' properties must be identifiers or literals'); } assertExpectedReactPropSyntax(key, elem.value, true); return true; @@ -152,13 +152,13 @@ module.exports = (context) => { var propValue = getPropValue(propType); switch (propValue) { case 1: return; - case 2: return context.report(declaration, key + ': ' + propType + ' is a misspelled prop type. Please correct spelling'); - default: return context.report(declaration, key + ': ' + propType + ' is not a known prop type'); + case 2: return context.report(declaration, "property '" + key + "': " + propType + ' is a misspelled prop type. Please correct spelling'); + default: return context.report(declaration, "property '" + key + "': " + propType + ' is not a known prop type'); } } else if (declaration.type === 'FunctionExpression') { // ok } else { - context.report(declaration, key + ': unknown use of PropTypes'); + context.report(declaration, "property '" + key + '\': unknown use of PropTypes'); } } From 6435731f0e4da4c99389b791dd32e1fd60935e12 Mon Sep 17 00:00:00 2001 From: Jonas Berlin Date: Fri, 24 Feb 2017 22:58:12 +0200 Subject: [PATCH 04/11] Several fixes to error reporting --- lib/rules/jsx-no-invalid-props.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/rules/jsx-no-invalid-props.js b/lib/rules/jsx-no-invalid-props.js index 08b381a..6352da9 100644 --- a/lib/rules/jsx-no-invalid-props.js +++ b/lib/rules/jsx-no-invalid-props.js @@ -48,8 +48,8 @@ module.exports = (context) => { } var argType = { - 3: "proptype", - 4: "JS type", + 3: "proptype or function", + 4: "JavaScript type", 5: "array with strings", 6: "array with proptypes", 7: "object with proptypes as values", @@ -98,14 +98,16 @@ module.exports = (context) => { var propType = declaration.callee.property.name; var propValue = getPropValue(propType); switch (propValue) { + case 1: return context.report(declaration, "property '" + key + "': " + propType + ' is a simple prop type, should not be called like a function'); + case 2: return context.report(declaration, "property '" + key + "': " + propType + ' is a misspelled prop type. Please correct spelling'); case 3: case 4: case 5: case 6: case 7: break; - default: return context.report(declaration, "property '" + key + "': " + propType + ' must be called like a function'); + default: return context.report(declaration, "property '" + key + "': " + propType + ' is not a known prop type'); } if (declaration.arguments.length !== 1) { - context.report(declaration, "property '" + key + "': " + propType + ' expects exactly one argument of type ' + argType[propValue]); + return context.report(declaration, "property '" + key + "': " + propType + ' expects exactly one argument of type ' + argType[propValue]); } var arg = declaration.arguments[0]; - if (((propValue == 3 || propValue == 4) && isArray(arg) || isObject(arg)) + if (((propValue == 3 || propValue == 4) && (isArray(arg) || isObject(arg))) || ((propValue == 5 || propValue == 6) && !isArray(arg)) || (propValue == 7 && !isObject(arg))) { return context.report(declaration, "property '" + key + "': " + propType + ' expects exactly one argument of type ' + argType[propValue]); @@ -153,6 +155,7 @@ module.exports = (context) => { switch (propValue) { case 1: return; case 2: return context.report(declaration, "property '" + key + "': " + propType + ' is a misspelled prop type. Please correct spelling'); + case 3: case 4: case 5: case 6: case 7: return context.report(declaration, "property '" + key + "': " + propType + ' expects to be called like a function with an argument of type ' + argType[propValue]); default: return context.report(declaration, "property '" + key + "': " + propType + ' is not a known prop type'); } } else if (declaration.type === 'FunctionExpression') { From adeeb2e1b0489a99d31aa4511801ddc77d4aec05 Mon Sep 17 00:00:00 2001 From: Jonas Berlin Date: Sat, 25 Feb 2017 00:49:19 +0200 Subject: [PATCH 05/11] Better reporting: also include isRequired --- lib/rules/jsx-no-invalid-props.js | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/rules/jsx-no-invalid-props.js b/lib/rules/jsx-no-invalid-props.js index 6352da9..1cdac45 100644 --- a/lib/rules/jsx-no-invalid-props.js +++ b/lib/rules/jsx-no-invalid-props.js @@ -84,33 +84,34 @@ module.exports = (context) => { } function assertExpectedReactPropSyntax(key, declaration, allowIsRequired) { + var origDeclaration = declaration; if (declaration.type === 'MemberExpression' && isIdentifier(declaration.property, "isRequired")) { if (!allowIsRequired) { - return context.report(declaration, "property '" + key + "': please move isRequired qualifier outside the oneOfType() declaration e.g. '(React.)PropTypes.oneOfType(...).isRequired'"); + return context.report(origDeclaration, "property '" + key + "': please move isRequired qualifier outside the oneOfType() declaration e.g. '(React.)PropTypes.oneOfType(...).isRequired'"); } declaration = declaration.object; } if (declaration.type === 'CallExpression') { if (!isPropTypesExpression(declaration.callee) || !isIdentifier(declaration.callee.property)) { - return context.report(declaration, "property '" + key + "': unsupported proptypes call syntax"); + return context.report(origDeclaration, "property '" + key + "': unsupported proptypes call syntax"); } var propType = declaration.callee.property.name; var propValue = getPropValue(propType); switch (propValue) { - case 1: return context.report(declaration, "property '" + key + "': " + propType + ' is a simple prop type, should not be called like a function'); - case 2: return context.report(declaration, "property '" + key + "': " + propType + ' is a misspelled prop type. Please correct spelling'); + case 1: return context.report(origDeclaration, "property '" + key + "': " + propType + ' is a simple prop type, should not be called like a function'); + case 2: return context.report(origDeclaration, "property '" + key + "': " + propType + ' is a misspelled prop type. Please correct spelling'); case 3: case 4: case 5: case 6: case 7: break; - default: return context.report(declaration, "property '" + key + "': " + propType + ' is not a known prop type'); + default: return context.report(origDeclaration, "property '" + key + "': " + propType + ' is not a known prop type'); } if (declaration.arguments.length !== 1) { - return context.report(declaration, "property '" + key + "': " + propType + ' expects exactly one argument of type ' + argType[propValue]); + return context.report(origDeclaration, "property '" + key + "': " + propType + ' expects exactly one argument of type ' + argType[propValue]); } var arg = declaration.arguments[0]; if (((propValue == 3 || propValue == 4) && (isArray(arg) || isObject(arg))) || ((propValue == 5 || propValue == 6) && !isArray(arg)) || (propValue == 7 && !isObject(arg))) { - return context.report(declaration, "property '" + key + "': " + propType + ' expects exactly one argument of type ' + argType[propValue]); + return context.report(origDeclaration, "property '" + key + "': " + propType + ' expects exactly one argument of type ' + argType[propValue]); } switch (propValue) { case 3: @@ -118,13 +119,13 @@ module.exports = (context) => { break; case 4: if (arg.type === 'Literal') { - context.report(declaration, "property '" + key + "': " + propType + ' argument must not be a literal'); + context.report(origDeclaration, "property '" + key + "': " + propType + ' argument must not be a literal'); } break; case 5: arg.elements.every(function (elem) { if (elem.type !== 'Literal') { - context.report(declaration, "property '" + key + "': " + propType + ' array entries must be literals'); + context.report(origDeclaration, "property '" + key + "': " + propType + ' array entries must be literals'); return false; } return true; @@ -138,11 +139,11 @@ module.exports = (context) => { case 7: arg.properties.every(function (elem) { if (elem.type !== 'Property') { - context.report(declaration, "property '" + key + "': " + propType + ' object must only consist of properties'); + context.report(origDeclaration, "property '" + key + "': " + propType + ' object must only consist of properties'); return false; } if (!isIdentifierOrRawLiteral(elem.key)) { - context.report(declaration, "property '" + key + "': " + propType + ' properties must be identifiers or literals'); + context.report(origDeclaration, "property '" + key + "': " + propType + ' properties must be identifiers or literals'); } assertExpectedReactPropSyntax(key, elem.value, true); return true; @@ -154,14 +155,14 @@ module.exports = (context) => { var propValue = getPropValue(propType); switch (propValue) { case 1: return; - case 2: return context.report(declaration, "property '" + key + "': " + propType + ' is a misspelled prop type. Please correct spelling'); - case 3: case 4: case 5: case 6: case 7: return context.report(declaration, "property '" + key + "': " + propType + ' expects to be called like a function with an argument of type ' + argType[propValue]); - default: return context.report(declaration, "property '" + key + "': " + propType + ' is not a known prop type'); + case 2: return context.report(origDeclaration, "property '" + key + "': " + propType + ' is a misspelled prop type. Please correct spelling'); + case 3: case 4: case 5: case 6: case 7: return context.report(origDeclaration, "property '" + key + "': " + propType + ' expects to be called like a function with an argument of type ' + argType[propValue]); + default: return context.report(origDeclaration, "property '" + key + "': " + propType + ' is not a valid PropType'); } } else if (declaration.type === 'FunctionExpression') { // ok } else { - context.report(declaration, "property '" + key + '\': unknown use of PropTypes'); + context.report(origDeclaration, "property '" + key + '\': unknown use of PropTypes'); } } From a451d61c6c9bc824db0076bbca5afb6f1ce72090 Mon Sep 17 00:00:00 2001 From: Jonas Berlin Date: Sat, 25 Feb 2017 00:49:33 +0200 Subject: [PATCH 06/11] Fix & add new tests --- tests/lib/rules/jsx-no-invalid-props.js | 357 +++++++++++++++++- .../rules/jsx-no-invalid-props/invalid-1.js | 56 +++ .../lib/rules/jsx-no-invalid-props/valid-1.js | 84 +++++ .../lib/rules/jsx-no-invalid-props/valid-2.js | 51 +++ 4 files changed, 528 insertions(+), 20 deletions(-) create mode 100644 tests/lib/rules/jsx-no-invalid-props/invalid-1.js create mode 100644 tests/lib/rules/jsx-no-invalid-props/valid-1.js create mode 100644 tests/lib/rules/jsx-no-invalid-props/valid-2.js diff --git a/tests/lib/rules/jsx-no-invalid-props.js b/tests/lib/rules/jsx-no-invalid-props.js index 18cd242..fcde929 100644 --- a/tests/lib/rules/jsx-no-invalid-props.js +++ b/tests/lib/rules/jsx-no-invalid-props.js @@ -1,6 +1,8 @@ 'use strict'; require('babel-eslint'); +var fs = require('fs'); +var path = require('path'); const rule = require('../../../lib/rules/jsx-no-invalid-props'); const RuleTester = require('eslint').RuleTester; @@ -9,6 +11,14 @@ const ruleTester = new RuleTester(); ruleTester.run('jsx-no-invalid-props', rule, { valid: [ + { + code: fs.readFileSync(path.join(__dirname, 'jsx-no-invalid-props', 'valid-1.js'), 'utf-8') + }, + { + code: fs.readFileSync(path.join(__dirname, 'jsx-no-invalid-props', 'valid-2.js'), 'utf-8') + }, + ], + invalid: [ { code: [ 'import React, {PropTypes} from "react";', @@ -63,9 +73,320 @@ ruleTester.run('jsx-no-invalid-props', rule, { jsx: true, }, }, + errors: [ + { + message: 'property \'h\': arrayOf expects to be called like a function with an argument of type proptype or function', + line: 21, + column: 4, + type: 'MemberExpression' + }, + { + message: 'property \'j\': instanceOf expects to be called like a function with an argument of type JavaScript type', + line: 23, + column: 4, + type: 'MemberExpression' + }, + { + message: 'property \'l\': objectOf expects to be called like a function with an argument of type proptype or function', + line: 25, + column: 4, + type: 'MemberExpression' + }, + { + message: 'property \'m\': oneOf expects to be called like a function with an argument of type array with strings', + line: 26, + column: 4, + type: 'MemberExpression' + }, + { + message: 'property \'n\': oneOfType expects to be called like a function with an argument of type array with proptypes', + line: 27, + column: 4, + type: 'MemberExpression' + }, + { + message: 'property \'o\': shape expects to be called like a function with an argument of type object with proptypes as values', + line: 28, + column: 4, + type: 'MemberExpression' + }, + { + message: 'property \'w\': arrayOf expects to be called like a function with an argument of type proptype or function', + line: 36, + column: 4, + type: 'MemberExpression' + }, + { + message: 'property \'y\': instanceOf expects to be called like a function with an argument of type JavaScript type', + line: 38, + column: 4, + type: 'MemberExpression' + }, + { + message: 'property \'aa\': objectOf expects to be called like a function with an argument of type proptype or function', + line: 40, + column: 5, + type: 'MemberExpression' + }, + { + message: 'property \'ab\': oneOf expects to be called like a function with an argument of type array with strings', + line: 41, + column: 5, + type: 'MemberExpression' + }, + { + message: 'property \'ac\': oneOfType expects to be called like a function with an argument of type array with proptypes', + line: 42, + column: 5, + type: 'MemberExpression' + }, + { + message: 'property \'ad\': shape expects to be called like a function with an argument of type object with proptypes as values', + line: 43, + column: 5, + type: 'MemberExpression' + } + ] + }, + { + code: fs.readFileSync(path.join(__dirname, 'jsx-no-invalid-props', 'invalid-1.js'), 'utf-8'), + errors: [ + { + message: 'property \'badBool\': boolean is a misspelled prop type. Please correct spelling', + line: 9, + column: 12, + type: 'MemberExpression' + }, + { + message: 'property \'badFunc\': function is a misspelled prop type. Please correct spelling', + line: 10, + column: 12, + type: 'MemberExpression' + }, + { + message: 'property \'badUnknown\': unknown is not a valid PropType', + line: 11, + column: 15, + type: 'MemberExpression' + }, + { + message: 'property \'badMessage\': instanceOf argument must not be a literal', + line: 12, + column: 15, + type: 'CallExpression' + }, + { + message: 'property \'badEnum\': oneOf array entries must be literals', + line: 13, + column: 12, + type: 'CallExpression' + }, + { + message: 'property \'badUnion\': please move isRequired qualifier outside the oneOfType() declaration e.g. \'(React.)PropTypes.oneOfType(...).isRequired\'', + line: 15, + column: 41, + type: 'MemberExpression' + }, + { + message: 'property \'badUnion\': please move isRequired qualifier outside the oneOfType() declaration e.g. \'(React.)PropTypes.oneOfType(...).isRequired\'', + line: 16, + column: 41, + type: 'MemberExpression' + }, + { + message: 'property \'badArrayOf\': unknown use of PropTypes', + line: 18, + column: 39, + type: 'Literal' + }, + { + message: 'property \'badObjectOf\': unknown use of PropTypes', + line: 19, + column: 41, + type: 'Literal' + }, + { + message: 'property \'badObjectWithShape\': unknown use of PropTypes', + line: 21, + column: 54, + type: 'Literal' + }, + { + message: 'property \'badObjectWithShape\': unknown use of PropTypes', + line: 22, + column: 57, + type: 'Literal', + }, + { + message: 'property \'nonFuncArray\': array is a simple prop type, should not be called like a function', + line: 25, + column: 17, + type: 'CallExpression' + }, + { + message: 'property \'nonFuncBool\': bool is a simple prop type, should not be called like a function', + line: 26, + column: 16, + type: 'CallExpression' + }, + { + message: 'property \'nonFuncFunc\': func is a simple prop type, should not be called like a function', + line: 27, + column: 16, + type: 'CallExpression' + }, + { + message: 'property \'nonFuncNumber\': number is a simple prop type, should not be called like a function', + line: 28, + column: 18, + type: 'CallExpression' + }, + { + message: 'property \'nonFuncObject\': object is a simple prop type, should not be called like a function', + line: 29, + column: 18, + type: 'CallExpression' + }, + { + message: 'property \'nonFuncString\': string is a simple prop type, should not be called like a function', + line: 30, + column: 18, + type: 'CallExpression' + }, + { + message: 'property \'nonFuncSymbol\': symbol is a simple prop type, should not be called like a function', + line: 31, + column: 18, + type: 'CallExpression' + }, + { + message: 'property \'nonFuncNode\': node is a simple prop type, should not be called like a function', + line: 32, + column: 16, + type: 'CallExpression' + }, + { + message: 'property \'nonFuncElement\': element is a simple prop type, should not be called like a function', + line: 33, + column: 19, + type: 'CallExpression' + }, + { + message: 'property \'nonFuncAny\': any is a simple prop type, should not be called like a function', + line: 34, + column: 15, + type: 'CallExpression' + }, + { + message: 'property \'missingArgMessage\': instanceOf expects exactly one argument of type JavaScript type', + line: 36, + column: 22, + type: 'CallExpression' + }, + { + message: 'property \'missingArgEnum\': oneOf expects exactly one argument of type array with strings', + line: 37, + column: 19, + type: 'CallExpression' + }, + { + message: 'property \'missingArgUnion\': oneOfType expects exactly one argument of type array with proptypes', + line: 38, + column: 20, + type: 'CallExpression' + }, + { + message: 'property \'missingArgArrayOf\': arrayOf expects exactly one argument of type proptype or function', + line: 39, + column: 22, + type: 'CallExpression' + }, + { + message: 'property \'missingArgObjectOf\': objectOf expects exactly one argument of type proptype or function', + line: 40, + column: 23, + type: 'CallExpression' + }, + { + message: 'property \'missingArgObjectWithShape\': shape expects exactly one argument of type object with proptypes as values', + line: 41, + column: 30, + type: 'CallExpression' + }, + { + message: 'property \'tooManyArgMessage\': instanceOf expects exactly one argument of type JavaScript type', + line: 43, + column: 22, + type: 'CallExpression' + }, + { + message: 'property \'tooManyArgEnum\': oneOf expects exactly one argument of type array with strings', + line: 44, + column: 19, + type: 'CallExpression' + }, + { + message: 'property \'tooManyArgUnion\': oneOfType expects exactly one argument of type array with proptypes', + line: 45, + column: 20, + type: 'CallExpression' + }, + { + message: 'property \'tooManyArgArrayOf\': arrayOf expects exactly one argument of type proptype or function', + line: 46, + column: 22, + type: 'CallExpression' + }, + { + message: 'property \'tooManyArgObjectOf\': objectOf expects exactly one argument of type proptype or function', + line: 47, + column: 23, + type: 'CallExpression' + }, + { + message: 'property \'tooManyArgObjectWithShape\': shape expects exactly one argument of type object with proptypes as values', + line: 48, + column: 30, + type: 'CallExpression' + }, + { + message: 'property \'badArgMessage\': instanceOf expects exactly one argument of type JavaScript type', + line: 50, + column: 18, + type: 'CallExpression' + }, + { + message: 'property \'badArgEnum\': oneOf expects exactly one argument of type array with strings', + line: 51, + column: 15, + type: 'CallExpression' + }, + { + message: 'property \'badArgUnion\': oneOfType expects exactly one argument of type array with proptypes', + line: 52, + column: 16, + type: 'CallExpression' + }, + { + message: 'property \'badArgArrayOf\': arrayOf expects exactly one argument of type proptype or function', + line: 53, + column: 18, + type: 'CallExpression' + }, + { + message: 'property \'badArgObjectOf\': objectOf expects exactly one argument of type proptype or function', + line: 54, + column: 19, + type: 'CallExpression' + }, + { + message: 'property \'badArgObjectWithShape\': shape expects exactly one argument of type object with proptypes as values', + line: 55, + column: 26, + type: 'CallExpression' + } + ] }, - ], - invalid: [ { code: [ 'import React, {PropTypes} from "react";', @@ -91,14 +412,12 @@ ruleTester.run('jsx-no-invalid-props', rule, { jsx: true, }, }, - errors: [ - { - message: 'arr is not a valid PropType', - line: 14, - column: 1, - type: 'Property', - }, - ], + errors: [{ + message: 'property \'a\': arr is not a valid PropType', + line: 14, + column: 4, + type: 'MemberExpression' + }] }, { code: [ @@ -125,14 +444,12 @@ ruleTester.run('jsx-no-invalid-props', rule, { jsx: true, }, }, - errors: [ - { - message: 'unknown use of PropTypes', - line: 14, - column: 1, - type: 'Property', - }, - ], - }, - ], + errors: [{ + message: 'property \'a\': unknown use of PropTypes', + line: 14, + column: 4, + type: 'Identifier' + }] + } + ] }); diff --git a/tests/lib/rules/jsx-no-invalid-props/invalid-1.js b/tests/lib/rules/jsx-no-invalid-props/invalid-1.js new file mode 100644 index 0000000..f3f1f81 --- /dev/null +++ b/tests/lib/rules/jsx-no-invalid-props/invalid-1.js @@ -0,0 +1,56 @@ +var React = require("react"); +/* + The content below this comment is loosely based on one of the examples from the below url: + https://github.com/facebook/react/blob/bc86ac4380/docs/docs/typechecking-with-proptypes.md + .. which is licensed under https://creativecommons.org/licenses/by/4.0/ + This set covers most of the inavlid permutations + */ +MyComponent.propTypes = { + badBool: React.PropTypes.boolean, + badFunc: React.PropTypes.function, + badUnknown: React.PropTypes.unknown, + badMessage: React.PropTypes.instanceOf("Object"), + badEnum: React.PropTypes.oneOf([React]), + badUnion: React.PropTypes.oneOfType([ + React.PropTypes.string.isRequired, + React.PropTypes.number.isRequired, + ]), + badArrayOf: React.PropTypes.arrayOf("Array"), + badObjectOf: React.PropTypes.objectOf("Object"), + badObjectWithShape: React.PropTypes.shape({ + color: "lol", + fontSize: 5 + }), + + nonFuncArray: React.PropTypes.array(), + nonFuncBool: React.PropTypes.bool(), + nonFuncFunc: React.PropTypes.func(), + nonFuncNumber: React.PropTypes.number(), + nonFuncObject: React.PropTypes.object(), + nonFuncString: React.PropTypes.string(), + nonFuncSymbol: React.PropTypes.symbol(), + nonFuncNode: React.PropTypes.node(), + nonFuncElement: React.PropTypes.element(), + nonFuncAny: React.PropTypes.any(), + + missingArgMessage: React.PropTypes.instanceOf(), + missingArgEnum: React.PropTypes.oneOf(), + missingArgUnion: React.PropTypes.oneOfType(), + missingArgArrayOf: React.PropTypes.arrayOf(), + missingArgObjectOf: React.PropTypes.objectOf(), + missingArgObjectWithShape: React.PropTypes.shape(), + + tooManyArgMessage: React.PropTypes.instanceOf("",""), + tooManyArgEnum: React.PropTypes.oneOf("",""), + tooManyArgUnion: React.PropTypes.oneOfType("",""), + tooManyArgArrayOf: React.PropTypes.arrayOf("",""), + tooManyArgObjectOf: React.PropTypes.objectOf("",""), + tooManyArgObjectWithShape: React.PropTypes.shape("",""), + + badArgMessage: React.PropTypes.instanceOf({}), + badArgEnum: React.PropTypes.oneOf({}), + badArgUnion: React.PropTypes.oneOfType({}), + badArgArrayOf: React.PropTypes.arrayOf([]), + badArgObjectOf: React.PropTypes.objectOf({}), + badArgObjectWithShape: React.PropTypes.shape([]), +}; diff --git a/tests/lib/rules/jsx-no-invalid-props/valid-1.js b/tests/lib/rules/jsx-no-invalid-props/valid-1.js new file mode 100644 index 0000000..9dee4e2 --- /dev/null +++ b/tests/lib/rules/jsx-no-invalid-props/valid-1.js @@ -0,0 +1,84 @@ +var React = require("react"); +/* + The content below this comment is a copy of one of the examples from the below url, with whitespace changes only: + https://github.com/facebook/react/blob/bc86ac4380/docs/docs/typechecking-with-proptypes.md + .. which is licensed under https://creativecommons.org/licenses/by/4.0/ + */ +MyComponent.propTypes = { + // You can declare that a prop is a specific JS primitive. By default, these + // are all optional. + optionalArray: React.PropTypes.array, + optionalBool: React.PropTypes.bool, + optionalFunc: React.PropTypes.func, + optionalNumber: React.PropTypes.number, + optionalObject: React.PropTypes.object, + optionalString: React.PropTypes.string, + optionalSymbol: React.PropTypes.symbol, + + // Anything that can be rendered: numbers, strings, elements or an array + // (or fragment) containing these types. + optionalNode: React.PropTypes.node, + + // A React element. + optionalElement: React.PropTypes.element, + + // You can also declare that a prop is an instance of a class. This uses + // JS's instanceof operator. + optionalMessage: React.PropTypes.instanceOf(Message), + + // You can ensure that your prop is limited to specific values by treating + // it as an enum. + optionalEnum: React.PropTypes.oneOf(['News', 'Photos']), + + // An object that could be one of many types + optionalUnion: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number, + React.PropTypes.instanceOf(Message) + ]), + + // An array of a certain type + optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), + + // An object with property values of a certain type + optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), + + // An object taking on a particular shape + optionalObjectWithShape: React.PropTypes.shape({ + color: React.PropTypes.string, + fontSize: React.PropTypes.number + }), + + // You can chain any of the above with `isRequired` to make sure a warning + // is shown if the prop isn't provided. + requiredFunc: React.PropTypes.func.isRequired, + + // A value of any data type + requiredAny: React.PropTypes.any.isRequired, + + // You can also specify a custom validator. It should return an Error + // object if the validation fails. Don't `console.warn` or throw, as this + // won't work inside `oneOfType`. + customProp: function (props, propName, componentName) { + if (!/matchme/.test(props[propName])) { + return new Error( + 'Invalid prop `' + propName + '` supplied to' + + ' `' + componentName + '`. Validation failed.' + ); + } + }, + + // You can also supply a custom validator to `arrayOf` and `objectOf`. + // It should return an Error object if the validation fails. The validator + // will be called for each key in the array or object. The first two + // arguments of the validator are the array or object itself, and the + // current item's key. + customArrayProp: React.PropTypes.arrayOf(function (propValue, key, componentName, location, propFullName) { + if (!/matchme/.test(propValue[key])) { + return new Error( + 'Invalid prop `' + propFullName + '` supplied to' + + ' `' + componentName + '`. Validation failed.' + ); + } + }) +}; diff --git a/tests/lib/rules/jsx-no-invalid-props/valid-2.js b/tests/lib/rules/jsx-no-invalid-props/valid-2.js new file mode 100644 index 0000000..77a28fe --- /dev/null +++ b/tests/lib/rules/jsx-no-invalid-props/valid-2.js @@ -0,0 +1,51 @@ +var React = require("react"); +/* + The content below this comment is loosely based on one of the examples from the below url: + https://github.com/facebook/react/blob/bc86ac4380/docs/docs/typechecking-with-proptypes.md + .. which is licensed under https://creativecommons.org/licenses/by/4.0/ + This set covers most of the remaining acceptable permutations not covered in valid-1.js + */ +MyComponent.propTypes = { + requiredArray: React.PropTypes.array.isRequired, + requiredBool: React.PropTypes.bool.isRequired, + requiredFunc: React.PropTypes.func.isRequired, + requiredNumber: React.PropTypes.number.isRequired, + requiredObject: React.PropTypes.object.isRequired, + requiredString: React.PropTypes.string.isRequired, + requiredSymbol: React.PropTypes.symbol.isRequired, + requiredNode: React.PropTypes.node.isRequired, + requiredElement: React.PropTypes.element.isRequired, + requiredMessage: React.PropTypes.instanceOf(Message).isRequired, + requiredEnum: React.PropTypes.oneOf(['News', 'Photos']).isRequired, + requiredUnion: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number, + React.PropTypes.instanceOf(Message) + ]).isRequired, + requiredArrayOf: React.PropTypes.arrayOf(React.PropTypes.number.isRequired).isRequired, + requiredObjectOf: React.PropTypes.objectOf(React.PropTypes.number.isRequired).isRequired, + requiredObjectWithShape: React.PropTypes.shape({ + color: React.PropTypes.string.isRequired, + fontSize: React.PropTypes.number.isRequired + }), + deepShape: React.PropTypes.shape({ + color: React.PropTypes.string.isRequired, + deep: React.PropTypes.shape({ + foo: React.PropTypes.string.isRequired, + union: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number, + ]), + + }) + }), + optionalAny: React.PropTypes.any, + customObjectProp: React.PropTypes.objectOf(function (propValue, key, componentName, location, propFullName) { + if (!/matchme/.test(propValue[key])) { + return new Error( + 'Invalid prop `' + propFullName + '` supplied to' + + ' `' + componentName + '`. Validation failed.' + ); + } + }) +}; From c36ae2b023d07076a956ef0cf05c2b649f8abd19 Mon Sep 17 00:00:00 2001 From: Jonas Berlin Date: Wed, 8 Mar 2017 23:14:45 +0200 Subject: [PATCH 07/11] Fix linting problems & add arrow function support --- lib/rules/jsx-no-invalid-props.js | 91 ++++--- tests/lib/rules/jsx-no-invalid-props.js | 255 ++++++++++-------- .../rules/jsx-no-invalid-props/invalid-1.js | 37 +-- .../lib/rules/jsx-no-invalid-props/valid-1.js | 13 +- .../lib/rules/jsx-no-invalid-props/valid-2.js | 60 +++-- 5 files changed, 255 insertions(+), 201 deletions(-) diff --git a/lib/rules/jsx-no-invalid-props.js b/lib/rules/jsx-no-invalid-props.js index 1cdac45..2127d57 100644 --- a/lib/rules/jsx-no-invalid-props.js +++ b/lib/rules/jsx-no-invalid-props.js @@ -47,13 +47,13 @@ module.exports = (context) => { return validProps[propName]; } - var argType = { - 3: "proptype or function", - 4: "JavaScript type", - 5: "array with strings", - 6: "array with proptypes", - 7: "object with proptypes as values", - } + const argType = { + 3: 'proptype or function', + 4: 'JavaScript type', + 5: 'array with strings', + 6: 'array with proptypes', + 7: 'object with proptypes as values', + }; function isIdentifier(n, ident) { return n.type === 'Identifier' && (ident === undefined || n.name === ident); @@ -66,7 +66,7 @@ module.exports = (context) => { if (n.type === 'Literal') { return n.raw; } - return ""; + return ''; } function isPropTypesExpression(n) { @@ -83,35 +83,36 @@ module.exports = (context) => { return n.type === 'ObjectExpression'; } - function assertExpectedReactPropSyntax(key, declaration, allowIsRequired) { - var origDeclaration = declaration; + function assertExpectedReactPropSyntax(key, origDeclaration, allowIsRequired) { + let declaration = origDeclaration; if (declaration.type === 'MemberExpression' && - isIdentifier(declaration.property, "isRequired")) { + isIdentifier(declaration.property, 'isRequired')) { if (!allowIsRequired) { - return context.report(origDeclaration, "property '" + key + "': please move isRequired qualifier outside the oneOfType() declaration e.g. '(React.)PropTypes.oneOfType(...).isRequired'"); + return context.report(origDeclaration, `property '${key}': please move isRequired qualifier outside the oneOfType() declaration e.g. '(React.)PropTypes.oneOfType(...).isRequired'`); } declaration = declaration.object; } if (declaration.type === 'CallExpression') { - if (!isPropTypesExpression(declaration.callee) || !isIdentifier(declaration.callee.property)) { - return context.report(origDeclaration, "property '" + key + "': unsupported proptypes call syntax"); + if (!isPropTypesExpression(declaration.callee) || + !isIdentifier(declaration.callee.property)) { + return context.report(origDeclaration, `property '${key}': unsupported proptypes call syntax`); } - var propType = declaration.callee.property.name; - var propValue = getPropValue(propType); + const propType = declaration.callee.property.name; + const propValue = getPropValue(propType); switch (propValue) { - case 1: return context.report(origDeclaration, "property '" + key + "': " + propType + ' is a simple prop type, should not be called like a function'); - case 2: return context.report(origDeclaration, "property '" + key + "': " + propType + ' is a misspelled prop type. Please correct spelling'); + case 1: return context.report(origDeclaration, `property '${key}': ${propType} is a simple prop type, should not be called like a function`); + case 2: return context.report(origDeclaration, `property '${key}': ${propType} is a misspelled prop type. Please correct spelling`); case 3: case 4: case 5: case 6: case 7: break; - default: return context.report(origDeclaration, "property '" + key + "': " + propType + ' is not a known prop type'); + default: return context.report(origDeclaration, `property '${key}': ${propType} is not a known prop type`); } if (declaration.arguments.length !== 1) { - return context.report(origDeclaration, "property '" + key + "': " + propType + ' expects exactly one argument of type ' + argType[propValue]); + return context.report(origDeclaration, `property '${key}': ${propType} expects exactly one argument of type ${argType[propValue]}`); } - var arg = declaration.arguments[0]; - if (((propValue == 3 || propValue == 4) && (isArray(arg) || isObject(arg))) - || ((propValue == 5 || propValue == 6) && !isArray(arg)) - || (propValue == 7 && !isObject(arg))) { - return context.report(origDeclaration, "property '" + key + "': " + propType + ' expects exactly one argument of type ' + argType[propValue]); + const arg = declaration.arguments[0]; + if (((propValue === 3 || propValue === 4) && (isArray(arg) || isObject(arg))) + || ((propValue === 5 || propValue === 6) && !isArray(arg)) + || (propValue === 7 && !isObject(arg))) { + return context.report(origDeclaration, `property '${key}': ${propType} expects exactly one argument of type ${argType[propValue]}`); } switch (propValue) { case 3: @@ -119,56 +120,60 @@ module.exports = (context) => { break; case 4: if (arg.type === 'Literal') { - context.report(origDeclaration, "property '" + key + "': " + propType + ' argument must not be a literal'); + context.report(origDeclaration, `property '${key}': ${propType} argument must not be a literal`); } break; case 5: - arg.elements.every(function (elem) { + arg.elements.every((elem) => { if (elem.type !== 'Literal') { - context.report(origDeclaration, "property '" + key + "': " + propType + ' array entries must be literals'); + context.report(origDeclaration, `property '${key}': ${propType} array entries must be literals`); return false; } return true; }); break; case 6: - arg.elements.forEach(function (elem) { - return assertExpectedReactPropSyntax(key, elem, false); - }); + arg.elements.forEach(elem => assertExpectedReactPropSyntax(key, elem, false)); break; case 7: - arg.properties.every(function (elem) { + arg.properties.every((elem) => { if (elem.type !== 'Property') { - context.report(origDeclaration, "property '" + key + "': " + propType + ' object must only consist of properties'); + context.report(origDeclaration, `property '${key}': ${propType} object must only consist of properties`); return false; } if (!isIdentifierOrRawLiteral(elem.key)) { - context.report(origDeclaration, "property '" + key + "': " + propType + ' properties must be identifiers or literals'); + context.report(origDeclaration, `property '${key}': ${propType} properties must be identifiers or literals`); } assertExpectedReactPropSyntax(key, elem.value, true); return true; }); break; + default: + throw new Error(`Unhandled propValue ${propValue}`); } } else if (isPropTypesExpression(declaration) && isIdentifier(declaration.property)) { - var propType = declaration.property.name; - var propValue = getPropValue(propType); + const propType = declaration.property.name; + const propValue = getPropValue(propType); switch (propValue) { - case 1: return; - case 2: return context.report(origDeclaration, "property '" + key + "': " + propType + ' is a misspelled prop type. Please correct spelling'); - case 3: case 4: case 5: case 6: case 7: return context.report(origDeclaration, "property '" + key + "': " + propType + ' expects to be called like a function with an argument of type ' + argType[propValue]); - default: return context.report(origDeclaration, "property '" + key + "': " + propType + ' is not a valid PropType'); + case 1: return null; + case 2: return context.report(origDeclaration, `property '${key}': ${propType} is a misspelled prop type. Please correct spelling`); + case 3: case 4: case 5: case 6: case 7: return context.report(origDeclaration, `property '${key}': ${propType} expects to be called like a function with an argument of type ${argType[propValue]}`); + default: return context.report(origDeclaration, `property '${key}': ${propType} is not a valid PropType`); } - } else if (declaration.type === 'FunctionExpression') { + } else if (declaration.type === 'FunctionExpression' || + declaration.type === 'ArrowFunctionExpression') { // ok } else { - context.report(origDeclaration, "property '" + key + '\': unknown use of PropTypes'); + // console.log(declaration); + return context.report(origDeclaration, `property '${key}': unknown use of PropTypes`); } + return null; } function checkProperties(declarations) { declarations.forEach((declaration) => { - assertExpectedReactPropSyntax(isIdentifierOrRawLiteral(declaration.key), declaration.value, true); + const identifierOrRawLiteral = isIdentifierOrRawLiteral(declaration.key); + assertExpectedReactPropSyntax(identifierOrRawLiteral, declaration.value, true); }); } diff --git a/tests/lib/rules/jsx-no-invalid-props.js b/tests/lib/rules/jsx-no-invalid-props.js index fcde929..c0eb606 100644 --- a/tests/lib/rules/jsx-no-invalid-props.js +++ b/tests/lib/rules/jsx-no-invalid-props.js @@ -1,8 +1,8 @@ 'use strict'; require('babel-eslint'); -var fs = require('fs'); -var path = require('path'); +const fs = require('fs'); +const path = require('path'); const rule = require('../../../lib/rules/jsx-no-invalid-props'); const RuleTester = require('eslint').RuleTester; @@ -12,10 +12,24 @@ const ruleTester = new RuleTester(); ruleTester.run('jsx-no-invalid-props', rule, { valid: [ { - code: fs.readFileSync(path.join(__dirname, 'jsx-no-invalid-props', 'valid-1.js'), 'utf-8') + code: fs.readFileSync(path.join(__dirname, 'jsx-no-invalid-props', 'valid-1.js'), 'utf-8'), + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, }, { - code: fs.readFileSync(path.join(__dirname, 'jsx-no-invalid-props', 'valid-2.js'), 'utf-8') + code: fs.readFileSync(path.join(__dirname, 'jsx-no-invalid-props', 'valid-2.js'), 'utf-8'), + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, }, ], invalid: [ @@ -78,314 +92,321 @@ ruleTester.run('jsx-no-invalid-props', rule, { message: 'property \'h\': arrayOf expects to be called like a function with an argument of type proptype or function', line: 21, column: 4, - type: 'MemberExpression' + type: 'MemberExpression', }, { message: 'property \'j\': instanceOf expects to be called like a function with an argument of type JavaScript type', line: 23, column: 4, - type: 'MemberExpression' + type: 'MemberExpression', }, { message: 'property \'l\': objectOf expects to be called like a function with an argument of type proptype or function', line: 25, column: 4, - type: 'MemberExpression' + type: 'MemberExpression', }, { message: 'property \'m\': oneOf expects to be called like a function with an argument of type array with strings', line: 26, column: 4, - type: 'MemberExpression' + type: 'MemberExpression', }, { message: 'property \'n\': oneOfType expects to be called like a function with an argument of type array with proptypes', line: 27, column: 4, - type: 'MemberExpression' + type: 'MemberExpression', }, { message: 'property \'o\': shape expects to be called like a function with an argument of type object with proptypes as values', line: 28, column: 4, - type: 'MemberExpression' + type: 'MemberExpression', }, { message: 'property \'w\': arrayOf expects to be called like a function with an argument of type proptype or function', line: 36, column: 4, - type: 'MemberExpression' + type: 'MemberExpression', }, { message: 'property \'y\': instanceOf expects to be called like a function with an argument of type JavaScript type', line: 38, column: 4, - type: 'MemberExpression' + type: 'MemberExpression', }, { message: 'property \'aa\': objectOf expects to be called like a function with an argument of type proptype or function', line: 40, column: 5, - type: 'MemberExpression' + type: 'MemberExpression', }, { message: 'property \'ab\': oneOf expects to be called like a function with an argument of type array with strings', line: 41, column: 5, - type: 'MemberExpression' + type: 'MemberExpression', }, { message: 'property \'ac\': oneOfType expects to be called like a function with an argument of type array with proptypes', line: 42, column: 5, - type: 'MemberExpression' + type: 'MemberExpression', }, { message: 'property \'ad\': shape expects to be called like a function with an argument of type object with proptypes as values', line: 43, column: 5, - type: 'MemberExpression' - } - ] + type: 'MemberExpression', + }, + ], }, { code: fs.readFileSync(path.join(__dirname, 'jsx-no-invalid-props', 'invalid-1.js'), 'utf-8'), + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, errors: [ { message: 'property \'badBool\': boolean is a misspelled prop type. Please correct spelling', - line: 9, + line: 14, column: 12, - type: 'MemberExpression' + type: 'MemberExpression', }, { message: 'property \'badFunc\': function is a misspelled prop type. Please correct spelling', - line: 10, + line: 15, column: 12, - type: 'MemberExpression' + type: 'MemberExpression', }, { message: 'property \'badUnknown\': unknown is not a valid PropType', - line: 11, + line: 16, column: 15, - type: 'MemberExpression' + type: 'MemberExpression', }, { message: 'property \'badMessage\': instanceOf argument must not be a literal', - line: 12, + line: 17, column: 15, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'badEnum\': oneOf array entries must be literals', - line: 13, + line: 18, column: 12, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'badUnion\': please move isRequired qualifier outside the oneOfType() declaration e.g. \'(React.)PropTypes.oneOfType(...).isRequired\'', - line: 15, - column: 41, - type: 'MemberExpression' + line: 20, + column: 5, + type: 'MemberExpression', }, { message: 'property \'badUnion\': please move isRequired qualifier outside the oneOfType() declaration e.g. \'(React.)PropTypes.oneOfType(...).isRequired\'', - line: 16, - column: 41, - type: 'MemberExpression' + line: 21, + column: 5, + type: 'MemberExpression', }, { message: 'property \'badArrayOf\': unknown use of PropTypes', - line: 18, + line: 23, column: 39, - type: 'Literal' + type: 'Literal', }, { message: 'property \'badObjectOf\': unknown use of PropTypes', - line: 19, + line: 24, column: 41, - type: 'Literal' + type: 'Literal', }, { message: 'property \'badObjectWithShape\': unknown use of PropTypes', - line: 21, - column: 54, - type: 'Literal' + line: 26, + column: 12, + type: 'Literal', }, { message: 'property \'badObjectWithShape\': unknown use of PropTypes', - line: 22, - column: 57, + line: 27, + column: 15, type: 'Literal', }, { message: 'property \'nonFuncArray\': array is a simple prop type, should not be called like a function', - line: 25, + line: 30, column: 17, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'nonFuncBool\': bool is a simple prop type, should not be called like a function', - line: 26, + line: 31, column: 16, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'nonFuncFunc\': func is a simple prop type, should not be called like a function', - line: 27, + line: 32, column: 16, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'nonFuncNumber\': number is a simple prop type, should not be called like a function', - line: 28, + line: 33, column: 18, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'nonFuncObject\': object is a simple prop type, should not be called like a function', - line: 29, + line: 34, column: 18, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'nonFuncString\': string is a simple prop type, should not be called like a function', - line: 30, + line: 35, column: 18, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'nonFuncSymbol\': symbol is a simple prop type, should not be called like a function', - line: 31, + line: 36, column: 18, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'nonFuncNode\': node is a simple prop type, should not be called like a function', - line: 32, + line: 37, column: 16, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'nonFuncElement\': element is a simple prop type, should not be called like a function', - line: 33, + line: 38, column: 19, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'nonFuncAny\': any is a simple prop type, should not be called like a function', - line: 34, + line: 39, column: 15, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'missingArgMessage\': instanceOf expects exactly one argument of type JavaScript type', - line: 36, + line: 41, column: 22, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'missingArgEnum\': oneOf expects exactly one argument of type array with strings', - line: 37, + line: 42, column: 19, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'missingArgUnion\': oneOfType expects exactly one argument of type array with proptypes', - line: 38, + line: 43, column: 20, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'missingArgArrayOf\': arrayOf expects exactly one argument of type proptype or function', - line: 39, + line: 44, column: 22, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'missingArgObjectOf\': objectOf expects exactly one argument of type proptype or function', - line: 40, + line: 45, column: 23, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'missingArgObjectWithShape\': shape expects exactly one argument of type object with proptypes as values', - line: 41, + line: 46, column: 30, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'tooManyArgMessage\': instanceOf expects exactly one argument of type JavaScript type', - line: 43, + line: 48, column: 22, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'tooManyArgEnum\': oneOf expects exactly one argument of type array with strings', - line: 44, + line: 49, column: 19, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'tooManyArgUnion\': oneOfType expects exactly one argument of type array with proptypes', - line: 45, + line: 50, column: 20, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'tooManyArgArrayOf\': arrayOf expects exactly one argument of type proptype or function', - line: 46, + line: 51, column: 22, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'tooManyArgObjectOf\': objectOf expects exactly one argument of type proptype or function', - line: 47, + line: 52, column: 23, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'tooManyArgObjectWithShape\': shape expects exactly one argument of type object with proptypes as values', - line: 48, + line: 53, column: 30, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'badArgMessage\': instanceOf expects exactly one argument of type JavaScript type', - line: 50, + line: 55, column: 18, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'badArgEnum\': oneOf expects exactly one argument of type array with strings', - line: 51, + line: 56, column: 15, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'badArgUnion\': oneOfType expects exactly one argument of type array with proptypes', - line: 52, + line: 57, column: 16, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'badArgArrayOf\': arrayOf expects exactly one argument of type proptype or function', - line: 53, + line: 58, column: 18, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'badArgObjectOf\': objectOf expects exactly one argument of type proptype or function', - line: 54, + line: 59, column: 19, - type: 'CallExpression' + type: 'CallExpression', }, { message: 'property \'badArgObjectWithShape\': shape expects exactly one argument of type object with proptypes as values', - line: 55, + line: 60, column: 26, - type: 'CallExpression' - } - ] + type: 'CallExpression', + }, + ], }, { code: [ @@ -412,12 +433,14 @@ ruleTester.run('jsx-no-invalid-props', rule, { jsx: true, }, }, - errors: [{ - message: 'property \'a\': arr is not a valid PropType', - line: 14, - column: 4, - type: 'MemberExpression' - }] + errors: [ + { + message: 'property \'a\': arr is not a valid PropType', + line: 14, + column: 4, + type: 'MemberExpression', + }, + ], }, { code: [ @@ -444,12 +467,14 @@ ruleTester.run('jsx-no-invalid-props', rule, { jsx: true, }, }, - errors: [{ - message: 'property \'a\': unknown use of PropTypes', - line: 14, - column: 4, - type: 'Identifier' - }] - } - ] + errors: [ + { + message: 'property \'a\': unknown use of PropTypes', + line: 14, + column: 4, + type: 'Identifier', + }, + ], + }, + ], }); diff --git a/tests/lib/rules/jsx-no-invalid-props/invalid-1.js b/tests/lib/rules/jsx-no-invalid-props/invalid-1.js index f3f1f81..5239081 100644 --- a/tests/lib/rules/jsx-no-invalid-props/invalid-1.js +++ b/tests/lib/rules/jsx-no-invalid-props/invalid-1.js @@ -1,4 +1,9 @@ -var React = require("react"); +/* eslint import/no-extraneous-dependencies: "off", import/no-unresolved: "off" */ + +const React = require('react'); + +const MyComponent = {}; + /* The content below this comment is loosely based on one of the examples from the below url: https://github.com/facebook/react/blob/bc86ac4380/docs/docs/typechecking-with-proptypes.md @@ -9,18 +14,18 @@ MyComponent.propTypes = { badBool: React.PropTypes.boolean, badFunc: React.PropTypes.function, badUnknown: React.PropTypes.unknown, - badMessage: React.PropTypes.instanceOf("Object"), + badMessage: React.PropTypes.instanceOf('Object'), badEnum: React.PropTypes.oneOf([React]), badUnion: React.PropTypes.oneOfType([ - React.PropTypes.string.isRequired, - React.PropTypes.number.isRequired, - ]), - badArrayOf: React.PropTypes.arrayOf("Array"), - badObjectOf: React.PropTypes.objectOf("Object"), + React.PropTypes.string.isRequired, + React.PropTypes.number.isRequired, + ]), + badArrayOf: React.PropTypes.arrayOf('Array'), + badObjectOf: React.PropTypes.objectOf('Object'), badObjectWithShape: React.PropTypes.shape({ - color: "lol", - fontSize: 5 - }), + color: 'lol', + fontSize: 5, + }), nonFuncArray: React.PropTypes.array(), nonFuncBool: React.PropTypes.bool(), @@ -40,12 +45,12 @@ MyComponent.propTypes = { missingArgObjectOf: React.PropTypes.objectOf(), missingArgObjectWithShape: React.PropTypes.shape(), - tooManyArgMessage: React.PropTypes.instanceOf("",""), - tooManyArgEnum: React.PropTypes.oneOf("",""), - tooManyArgUnion: React.PropTypes.oneOfType("",""), - tooManyArgArrayOf: React.PropTypes.arrayOf("",""), - tooManyArgObjectOf: React.PropTypes.objectOf("",""), - tooManyArgObjectWithShape: React.PropTypes.shape("",""), + tooManyArgMessage: React.PropTypes.instanceOf('', ''), + tooManyArgEnum: React.PropTypes.oneOf('', ''), + tooManyArgUnion: React.PropTypes.oneOfType('', ''), + tooManyArgArrayOf: React.PropTypes.arrayOf('', ''), + tooManyArgObjectOf: React.PropTypes.objectOf('', ''), + tooManyArgObjectWithShape: React.PropTypes.shape('', ''), badArgMessage: React.PropTypes.instanceOf({}), badArgEnum: React.PropTypes.oneOf({}), diff --git a/tests/lib/rules/jsx-no-invalid-props/valid-1.js b/tests/lib/rules/jsx-no-invalid-props/valid-1.js index 9dee4e2..f26da02 100644 --- a/tests/lib/rules/jsx-no-invalid-props/valid-1.js +++ b/tests/lib/rules/jsx-no-invalid-props/valid-1.js @@ -1,6 +1,15 @@ -var React = require("react"); +/* eslint import/no-extraneous-dependencies: "off", import/no-unresolved: "off", indent: "off" */ +/* eslint comma-dangle: "off", consistent-return: "off", prefer-arrow-callback: "off" */ +/* eslint func-names: "off", object-shorthand: "off", prefer-template: "off", max-len: "off" */ + +const React = require('react'); + +const MyComponent = {}; +const Message = {}; + /* - The content below this comment is a copy of one of the examples from the below url, with whitespace changes only: + The content below this comment is a copy of one of the examples from the below url, with + whitespace changes only: https://github.com/facebook/react/blob/bc86ac4380/docs/docs/typechecking-with-proptypes.md .. which is licensed under https://creativecommons.org/licenses/by/4.0/ */ diff --git a/tests/lib/rules/jsx-no-invalid-props/valid-2.js b/tests/lib/rules/jsx-no-invalid-props/valid-2.js index 77a28fe..973124c 100644 --- a/tests/lib/rules/jsx-no-invalid-props/valid-2.js +++ b/tests/lib/rules/jsx-no-invalid-props/valid-2.js @@ -1,4 +1,10 @@ -var React = require("react"); +/* eslint import/no-extraneous-dependencies: "off", import/no-unresolved: "off" */ + +const React = require('react'); + +const MyComponent = {}; +const Message = {}; + /* The content below this comment is loosely based on one of the examples from the below url: https://github.com/facebook/react/blob/bc86ac4380/docs/docs/typechecking-with-proptypes.md @@ -18,34 +24,38 @@ MyComponent.propTypes = { requiredMessage: React.PropTypes.instanceOf(Message).isRequired, requiredEnum: React.PropTypes.oneOf(['News', 'Photos']).isRequired, requiredUnion: React.PropTypes.oneOfType([ - React.PropTypes.string, - React.PropTypes.number, - React.PropTypes.instanceOf(Message) - ]).isRequired, + React.PropTypes.string, + React.PropTypes.number, + React.PropTypes.instanceOf(Message), + ]).isRequired, requiredArrayOf: React.PropTypes.arrayOf(React.PropTypes.number.isRequired).isRequired, requiredObjectOf: React.PropTypes.objectOf(React.PropTypes.number.isRequired).isRequired, requiredObjectWithShape: React.PropTypes.shape({ - color: React.PropTypes.string.isRequired, - fontSize: React.PropTypes.number.isRequired - }), + color: React.PropTypes.string.isRequired, + fontSize: React.PropTypes.number.isRequired, + }), deepShape: React.PropTypes.shape({ - color: React.PropTypes.string.isRequired, - deep: React.PropTypes.shape({ - foo: React.PropTypes.string.isRequired, - union: React.PropTypes.oneOfType([ - React.PropTypes.string, - React.PropTypes.number, - ]), - - }) - }), + color: React.PropTypes.string.isRequired, + deep: React.PropTypes.shape({ + foo: React.PropTypes.string.isRequired, + union: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number, + ]), + }), + }), optionalAny: React.PropTypes.any, - customObjectProp: React.PropTypes.objectOf(function (propValue, key, componentName, location, propFullName) { - if (!/matchme/.test(propValue[key])) { - return new Error( - 'Invalid prop `' + propFullName + '` supplied to' + - ' `' + componentName + '`. Validation failed.' - ); + customProp(props, propName, componentName) { + if (!/matchme/.test(props[propName])) { + return new Error(`error ${componentName}`); } - }) + return null; + }, + customObjectProp: + React.PropTypes.objectOf((propValue, key, componentName, location, propFullName) => { + if (!/matchme/.test(propValue[key])) { + return new Error(`error ${propFullName}`); + } + return null; + }), }; From 4ead2b7a28cf4f263d37f014fa279d400b441f83 Mon Sep 17 00:00:00 2001 From: Jonas Berlin Date: Wed, 8 Mar 2017 23:59:24 +0200 Subject: [PATCH 08/11] Extract test code to separate files --- tests/lib/rules/jsx-no-invalid-props.js | 139 ++++-------------- .../rules/jsx-no-invalid-props/invalid-2.js | 38 +++++ .../rules/jsx-no-invalid-props/invalid-3.js | 9 ++ .../rules/jsx-no-invalid-props/invalid-4.js | 9 ++ 4 files changed, 87 insertions(+), 108 deletions(-) create mode 100644 tests/lib/rules/jsx-no-invalid-props/invalid-2.js create mode 100644 tests/lib/rules/jsx-no-invalid-props/invalid-3.js create mode 100644 tests/lib/rules/jsx-no-invalid-props/invalid-4.js diff --git a/tests/lib/rules/jsx-no-invalid-props.js b/tests/lib/rules/jsx-no-invalid-props.js index c0eb606..497ddf1 100644 --- a/tests/lib/rules/jsx-no-invalid-props.js +++ b/tests/lib/rules/jsx-no-invalid-props.js @@ -34,52 +34,7 @@ ruleTester.run('jsx-no-invalid-props', rule, { ], invalid: [ { - code: [ - 'import React, {PropTypes} from "react";', - 'export default class TestComponent extends React.Component {', - 'constructor(props) {', - 'super(props);', - 'this.state = {};', - '}', - 'render() {', - 'return (', - '
', - ');', - '}', - '}', - 'TestComponent.propTypes = {', - 'a: PropTypes.array,', - 'b: PropTypes.bool,', - 'c: PropTypes.func,', - 'd: PropTypes.number,', - 'e: PropTypes.object,', - 'f: PropTypes.string,', - 'g: PropTypes.any,', - 'h: PropTypes.arrayOf,', - 'i: PropTypes.element,', - 'j: PropTypes.instanceOf,', - 'k: PropTypes.node,', - 'l: PropTypes.objectOf,', - 'm: PropTypes.oneOf,', - 'n: PropTypes.oneOfType,', - 'o: PropTypes.shape,', - 'p: PropTypes.array,', - 'q: PropTypes.bool,', - 'r: PropTypes.func,', - 's: PropTypes.number,', - 't: PropTypes.object,', - 'u: PropTypes.string,', - 'v: PropTypes.any,', - 'w: PropTypes.arrayOf,', - 'x: PropTypes.element,', - 'y: PropTypes.instanceOf,', - 'z: PropTypes.node,', - 'aa: PropTypes.objectOf,', - 'ab: PropTypes.oneOf,', - 'ac: PropTypes.oneOfType,', - 'ad: PropTypes.shape', - '};', - ].join('\n'), + code: fs.readFileSync(path.join(__dirname, 'jsx-no-invalid-props', 'invalid-2.js'), 'utf-8'), parserOptions: { ecmaVersion: 6, sourceType: 'module', @@ -90,74 +45,74 @@ ruleTester.run('jsx-no-invalid-props', rule, { errors: [ { message: 'property \'h\': arrayOf expects to be called like a function with an argument of type proptype or function', - line: 21, - column: 4, + line: 15, + column: 6, type: 'MemberExpression', }, { message: 'property \'j\': instanceOf expects to be called like a function with an argument of type JavaScript type', - line: 23, - column: 4, + line: 17, + column: 6, type: 'MemberExpression', }, { message: 'property \'l\': objectOf expects to be called like a function with an argument of type proptype or function', - line: 25, - column: 4, + line: 19, + column: 6, type: 'MemberExpression', }, { message: 'property \'m\': oneOf expects to be called like a function with an argument of type array with strings', - line: 26, - column: 4, + line: 20, + column: 6, type: 'MemberExpression', }, { message: 'property \'n\': oneOfType expects to be called like a function with an argument of type array with proptypes', - line: 27, - column: 4, + line: 21, + column: 6, type: 'MemberExpression', }, { message: 'property \'o\': shape expects to be called like a function with an argument of type object with proptypes as values', - line: 28, - column: 4, + line: 22, + column: 6, type: 'MemberExpression', }, { message: 'property \'w\': arrayOf expects to be called like a function with an argument of type proptype or function', - line: 36, - column: 4, + line: 30, + column: 6, type: 'MemberExpression', }, { message: 'property \'y\': instanceOf expects to be called like a function with an argument of type JavaScript type', - line: 38, - column: 4, + line: 32, + column: 6, type: 'MemberExpression', }, { message: 'property \'aa\': objectOf expects to be called like a function with an argument of type proptype or function', - line: 40, - column: 5, + line: 34, + column: 7, type: 'MemberExpression', }, { message: 'property \'ab\': oneOf expects to be called like a function with an argument of type array with strings', - line: 41, - column: 5, + line: 35, + column: 7, type: 'MemberExpression', }, { message: 'property \'ac\': oneOfType expects to be called like a function with an argument of type array with proptypes', - line: 42, - column: 5, + line: 36, + column: 7, type: 'MemberExpression', }, { message: 'property \'ad\': shape expects to be called like a function with an argument of type object with proptypes as values', - line: 43, - column: 5, + line: 37, + column: 7, type: 'MemberExpression', }, ], @@ -409,23 +364,7 @@ ruleTester.run('jsx-no-invalid-props', rule, { ], }, { - code: [ - 'import React, {PropTypes} from "react";', - 'export default class TestComponent extends React.Component {', - 'constructor(props) {', - 'super(props);', - 'this.state = {};', - '}', - 'render() {', - 'return (', - '
', - ');', - '}', - '}', - 'TestComponent.propTypes = {', - 'a: PropTypes.arr,', - '};', - ].join('\n'), + code: fs.readFileSync(path.join(__dirname, 'jsx-no-invalid-props', 'invalid-3.js'), 'utf-8'), parserOptions: { ecmaVersion: 6, sourceType: 'module', @@ -436,30 +375,14 @@ ruleTester.run('jsx-no-invalid-props', rule, { errors: [ { message: 'property \'a\': arr is not a valid PropType', - line: 14, - column: 4, + line: 8, + column: 6, type: 'MemberExpression', }, ], }, { - code: [ - 'import React, {PropTypes} from "react";', - 'export default class TestComponent extends React.Component {', - 'constructor(props) {', - 'super(props);', - 'this.state = {};', - '}', - 'render() {', - 'return (', - '
', - ');', - '}', - '}', - 'TestComponent.propTypes = {', - 'a: PropTypes,', - '};', - ].join('\n'), + code: fs.readFileSync(path.join(__dirname, 'jsx-no-invalid-props', 'invalid-4.js'), 'utf-8'), parserOptions: { ecmaVersion: 6, sourceType: 'module', @@ -470,8 +393,8 @@ ruleTester.run('jsx-no-invalid-props', rule, { errors: [ { message: 'property \'a\': unknown use of PropTypes', - line: 14, - column: 4, + line: 8, + column: 6, type: 'Identifier', }, ], diff --git a/tests/lib/rules/jsx-no-invalid-props/invalid-2.js b/tests/lib/rules/jsx-no-invalid-props/invalid-2.js new file mode 100644 index 0000000..6696b2d --- /dev/null +++ b/tests/lib/rules/jsx-no-invalid-props/invalid-2.js @@ -0,0 +1,38 @@ +/* eslint import/no-extraneous-dependencies: "off", import/no-unresolved: "off" */ +/* eslint comma-dangle: "off", no-unused-vars: "off", object-curly-spacing: "off" */ +/* eslint import/extensions: "off", import/newline-after-import: "off" */ + +import React, {PropTypes} from 'react'; +const TestComponent = {}; +TestComponent.propTypes = { + a: PropTypes.array, + b: PropTypes.bool, + c: PropTypes.func, + d: PropTypes.number, + e: PropTypes.object, + f: PropTypes.string, + g: PropTypes.any, + h: PropTypes.arrayOf, + i: PropTypes.element, + j: PropTypes.instanceOf, + k: PropTypes.node, + l: PropTypes.objectOf, + m: PropTypes.oneOf, + n: PropTypes.oneOfType, + o: PropTypes.shape, + p: PropTypes.array, + q: PropTypes.bool, + r: PropTypes.func, + s: PropTypes.number, + t: PropTypes.object, + u: PropTypes.string, + v: PropTypes.any, + w: PropTypes.arrayOf, + x: PropTypes.element, + y: PropTypes.instanceOf, + z: PropTypes.node, + aa: PropTypes.objectOf, + ab: PropTypes.oneOf, + ac: PropTypes.oneOfType, + ad: PropTypes.shape +}; diff --git a/tests/lib/rules/jsx-no-invalid-props/invalid-3.js b/tests/lib/rules/jsx-no-invalid-props/invalid-3.js new file mode 100644 index 0000000..3a097d7 --- /dev/null +++ b/tests/lib/rules/jsx-no-invalid-props/invalid-3.js @@ -0,0 +1,9 @@ +/* eslint import/no-extraneous-dependencies: "off", import/no-unresolved: "off" */ +/* eslint comma-dangle: "off", no-unused-vars: "off", object-curly-spacing: "off" */ +/* eslint import/extensions: "off", import/newline-after-import: "off" */ + +import React, {PropTypes} from 'react'; +const TestComponent = {}; +TestComponent.propTypes = { + a: PropTypes.arr, +}; diff --git a/tests/lib/rules/jsx-no-invalid-props/invalid-4.js b/tests/lib/rules/jsx-no-invalid-props/invalid-4.js new file mode 100644 index 0000000..1b8946b --- /dev/null +++ b/tests/lib/rules/jsx-no-invalid-props/invalid-4.js @@ -0,0 +1,9 @@ +/* eslint import/no-extraneous-dependencies: "off", import/no-unresolved: "off" */ +/* eslint comma-dangle: "off", no-unused-vars: "off", object-curly-spacing: "off" */ +/* eslint import/extensions: "off", import/newline-after-import: "off" */ + +import React, {PropTypes} from 'react'; +const TestComponent = {}; +TestComponent.propTypes = { + a: PropTypes, +}; From 55b88299225ce8c982b91f61abe371c1770df2c1 Mon Sep 17 00:00:00 2001 From: Jonas Berlin Date: Thu, 9 Mar 2017 00:15:30 +0200 Subject: [PATCH 09/11] Improve documentation --- docs/rules/jsx-no-invalid-props.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/rules/jsx-no-invalid-props.md b/docs/rules/jsx-no-invalid-props.md index d101eb4..c250ebc 100644 --- a/docs/rules/jsx-no-invalid-props.md +++ b/docs/rules/jsx-no-invalid-props.md @@ -1,10 +1,14 @@ # JSX No invalid props (jsx-no-invalid-props) -A quick check to see if a typo has been made on a PropType which at the minimum is annoying, but can also lead to debugging red herrings +Validates all `x.propTypes = { ... }` statements for proper syntax. ## Rule Details -This rule checks propTypes on a class and validates the PropTypes against a static list from the React docs +This rule checks propTypes on a class and validates the PropTypes against a static list from the [React docs](https://github.com/facebook/react/blob/master/docs/docs/typechecking-with-proptypes.md). + +It supports arrays, shapes etc, nested to any depth. + +The validation is rather strict; hopefully it will not generate false error reports. Please [report](https://github.com/craigbilner/eslint-plugin-react-props/issues) any broken error reports you may encounter. ## Rule Options From 31784e08987a7f81b79296fb15879e93b97d0e6a Mon Sep 17 00:00:00 2001 From: Jonas Berlin Date: Thu, 9 Mar 2017 00:30:57 +0200 Subject: [PATCH 10/11] Add contextTypes support --- docs/rules/jsx-no-invalid-props.md | 2 +- lib/utilities.js | 8 ++++++-- tests/lib/rules/jsx-no-invalid-props.js | 18 ++++++++++++++++++ .../rules/jsx-no-invalid-props/invalid-5.js | 9 +++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 tests/lib/rules/jsx-no-invalid-props/invalid-5.js diff --git a/docs/rules/jsx-no-invalid-props.md b/docs/rules/jsx-no-invalid-props.md index c250ebc..94ea897 100644 --- a/docs/rules/jsx-no-invalid-props.md +++ b/docs/rules/jsx-no-invalid-props.md @@ -1,6 +1,6 @@ # JSX No invalid props (jsx-no-invalid-props) -Validates all `x.propTypes = { ... }` statements for proper syntax. +Validates all `x.propTypes = { ... }` and `x.contextTypes = { ... }` statements for proper syntax (also as class members). ## Rule Details diff --git a/lib/utilities.js b/lib/utilities.js index e64d35d..b8a9a63 100644 --- a/lib/utilities.js +++ b/lib/utilities.js @@ -2,6 +2,10 @@ Borrowed from https://github.com/yannickcr/eslint-plugin-react */ +function isTypesDecl(str) { + return str === 'propTypes' || str === 'contextTypes'; +} + /** * Checks if node is `propTypes` declaration * @param {ASTNode} node The AST node being checked. @@ -12,13 +16,13 @@ function isPropTypesDeclaration(context, node) { // (babel-eslint does not expose property name so we have to rely on tokens) if (node.type === 'ClassProperty') { const tokens = context.getFirstTokens(node, 2); - if (tokens[0].value === 'propTypes' || tokens[1].value === 'propTypes') { + if (isTypesDecl(tokens[0].value) || isTypesDecl(tokens[1].value)) { return true; } return false; } - return Boolean(node && node.name === 'propTypes'); + return Boolean(node && isTypesDecl(node.name)); } module.exports = { diff --git a/tests/lib/rules/jsx-no-invalid-props.js b/tests/lib/rules/jsx-no-invalid-props.js index 497ddf1..c0ee178 100644 --- a/tests/lib/rules/jsx-no-invalid-props.js +++ b/tests/lib/rules/jsx-no-invalid-props.js @@ -399,5 +399,23 @@ ruleTester.run('jsx-no-invalid-props', rule, { }, ], }, + { + code: fs.readFileSync(path.join(__dirname, 'jsx-no-invalid-props', 'invalid-5.js'), 'utf-8'), + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + errors: [ + { + message: 'property \'b\': unknown use of PropTypes', + line: 8, + column: 6, + type: 'Identifier', + }, + ], + }, ], }); diff --git a/tests/lib/rules/jsx-no-invalid-props/invalid-5.js b/tests/lib/rules/jsx-no-invalid-props/invalid-5.js new file mode 100644 index 0000000..150b888 --- /dev/null +++ b/tests/lib/rules/jsx-no-invalid-props/invalid-5.js @@ -0,0 +1,9 @@ +/* eslint import/no-extraneous-dependencies: "off", import/no-unresolved: "off" */ +/* eslint comma-dangle: "off", no-unused-vars: "off", object-curly-spacing: "off" */ +/* eslint import/extensions: "off", import/newline-after-import: "off" */ + +import React, {PropTypes} from 'react'; +const TestComponent = {}; +TestComponent.contextTypes = { + b: PropTypes, +}; From 3359cc47e880594a282cba665582c6504584f53d Mon Sep 17 00:00:00 2001 From: Jonas Berlin Date: Thu, 9 Mar 2017 00:31:53 +0200 Subject: [PATCH 11/11] Add test ensuring random PropTypes references are not validated --- tests/lib/rules/jsx-no-invalid-props.js | 10 ++++++++++ tests/lib/rules/jsx-no-invalid-props/ignored-1.js | 9 +++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/lib/rules/jsx-no-invalid-props/ignored-1.js diff --git a/tests/lib/rules/jsx-no-invalid-props.js b/tests/lib/rules/jsx-no-invalid-props.js index c0ee178..6e139eb 100644 --- a/tests/lib/rules/jsx-no-invalid-props.js +++ b/tests/lib/rules/jsx-no-invalid-props.js @@ -31,6 +31,16 @@ ruleTester.run('jsx-no-invalid-props', rule, { }, }, }, + { + code: fs.readFileSync(path.join(__dirname, 'jsx-no-invalid-props', 'ignored-1.js'), 'utf-8'), + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + }, ], invalid: [ { diff --git a/tests/lib/rules/jsx-no-invalid-props/ignored-1.js b/tests/lib/rules/jsx-no-invalid-props/ignored-1.js new file mode 100644 index 0000000..1e3ea5c --- /dev/null +++ b/tests/lib/rules/jsx-no-invalid-props/ignored-1.js @@ -0,0 +1,9 @@ +/* eslint import/no-extraneous-dependencies: "off", import/no-unresolved: "off" */ +/* eslint comma-dangle: "off", no-unused-vars: "off", object-curly-spacing: "off" */ +/* eslint import/extensions: "off", import/newline-after-import: "off" */ + +import React, {PropTypes} from 'react'; +const TestComponent = {}; +TestComponent.notTestedTypes = { + c: PropTypes, +};