From d7bff458f612c3d3bacce38ca0ca5ca2a2d5ed10 Mon Sep 17 00:00:00 2001 From: Joe Lencioni Date: Sun, 7 Feb 2016 20:45:32 -0800 Subject: [PATCH] Rename jsx-sort-prop-types to sort-prop-types This rule has nothing to do with JSX, so it doesn't make sense for "jsx" to appear in its name. To prevent breaking this rule for people, I have set up jsx-sort-prop-types as an alias with a deprecation warning. Eventually, we should remove it altogether. For the deprecation warning, I borrowed code from jsx-quotes. While I was at it, I corrected some minor wording issues in the documentation for this rule (mostly using "propTypes" instead of "propsTypes"). Fixes #87. --- ...-sort-prop-types.md => sort-prop-types.md} | 10 +- index.js | 2 + lib/rules/jsx-sort-prop-types.js | 137 +--- lib/rules/sort-prop-types.js | 154 +++++ tests/lib/rules/jsx-sort-prop-types.js | 4 +- tests/lib/rules/sort-prop-types.js | 599 ++++++++++++++++++ 6 files changed, 776 insertions(+), 130 deletions(-) rename docs/rules/{jsx-sort-prop-types.md => sort-prop-types.md} (81%) create mode 100644 lib/rules/sort-prop-types.js create mode 100644 tests/lib/rules/sort-prop-types.js diff --git a/docs/rules/jsx-sort-prop-types.md b/docs/rules/sort-prop-types.md similarity index 81% rename from docs/rules/jsx-sort-prop-types.md rename to docs/rules/sort-prop-types.md index bf419001e5..0e863e419e 100644 --- a/docs/rules/jsx-sort-prop-types.md +++ b/docs/rules/sort-prop-types.md @@ -1,10 +1,10 @@ -# Enforce propTypes declarations alphabetical sorting (jsx-sort-prop-types) +# Enforce propTypes declarations alphabetical sorting (sort-prop-types) -Some developers prefer to sort propsTypes declarations alphabetically to be able to find necessary declaration easier at the later time. Others feel that it adds complexity and becomes burden to maintain. +Some developers prefer to sort propTypes declarations alphabetically to be able to find necessary declaration easier at the later time. Others feel that it adds complexity and becomes burden to maintain. ## Rule Details -This rule checks all JSX components and verifies that all propsTypes declarations are sorted alphabetically. +This rule checks all components and verifies that all propTypes declarations are sorted alphabetically. The default configuration of the rule is case-sensitive. This rule is off by default. @@ -78,7 +78,7 @@ class Component extends React.Component { ```js ... -"jsx-sort-prop-types": [, { +"sort-prop-types": [, { "callbacksLast": , "ignoreCase": , "requiredFirst": , @@ -92,7 +92,7 @@ When `true` the rule ignores the case-sensitivity of the declarations order. ### `callbacksLast` -When `true`, prop types for props beginning with "on" must be listed after all other props: +When `true`, propTypes for props beginning with "on" must be listed after all other props: ```js var Component = React.createClass({ diff --git a/index.js b/index.js index 5dfbf5e682..b777e7f030 100644 --- a/index.js +++ b/index.js @@ -25,6 +25,7 @@ module.exports = { 'jsx-curly-spacing': require('./lib/rules/jsx-curly-spacing'), 'jsx-equals-spacing': require('./lib/rules/jsx-equals-spacing'), 'jsx-sort-props': require('./lib/rules/jsx-sort-props'), + 'sort-prop-types': require('./lib/rules/sort-prop-types'), 'jsx-sort-prop-types': require('./lib/rules/jsx-sort-prop-types'), 'jsx-boolean-value': require('./lib/rules/jsx-boolean-value'), 'sort-comp': require('./lib/rules/sort-comp'), @@ -66,6 +67,7 @@ module.exports = { 'jsx-curly-spacing': 0, 'jsx-equals-spacing': 0, 'jsx-sort-props': 0, + 'sort-prop-types': 0, 'jsx-sort-prop-types': 0, 'jsx-boolean-value': 0, 'sort-comp': 0, diff --git a/lib/rules/jsx-sort-prop-types.js b/lib/rules/jsx-sort-prop-types.js index 8dd0758acb..636b9a10a7 100644 --- a/lib/rules/jsx-sort-prop-types.js +++ b/lib/rules/jsx-sort-prop-types.js @@ -1,5 +1,6 @@ /** * @fileoverview Enforce propTypes declarations alphabetical sorting + * @deprecated */ 'use strict'; @@ -7,134 +8,24 @@ // Rule Definition // ------------------------------------------------------------------------------ -module.exports = function(context) { - - var configuration = context.options[0] || {}; - var requiredFirst = configuration.requiredFirst || false; - var callbacksLast = configuration.callbacksLast || false; - var ignoreCase = configuration.ignoreCase || false; - - /** - * Checks if node is `propTypes` declaration - * @param {ASTNode} node The AST node being checked. - * @returns {Boolean} True if node is `propTypes` declaration, false if not. - */ - function isPropTypesDeclaration(node) { - - // Special case for class properties - // (babel-eslint does not expose property name so we have to rely on tokens) - if (node.type === 'ClassProperty') { - var tokens = context.getFirstTokens(node, 2); - return (tokens[0] && tokens[0].value === 'propTypes') || - (tokens[1] && tokens[1].value === 'propTypes'); - } - - return Boolean( - node && - node.name === 'propTypes' - ); - } - - function getKey(node) { - return node.key.type === 'Identifier' ? node.key.name : node.key.value; - } - - function getValueName(node) { - return node.value.property && node.value.property.name; - } - - function isCallbackPropName(propName) { - return /^on[A-Z]/.test(propName); - } - - function isRequiredProp(node) { - return getValueName(node) === 'isRequired'; - } - - /** - * Checks if propTypes declarations are sorted - * @param {Array} declarations The array of AST nodes being checked. - * @returns {void} - */ - function checkSorted(declarations) { - declarations.reduce(function(prev, curr) { - var prevPropName = getKey(prev); - var currentPropName = getKey(curr); - var previousIsRequired = isRequiredProp(prev); - var currentIsRequired = isRequiredProp(curr); - var previousIsCallback = isCallbackPropName(prevPropName); - var currentIsCallback = isCallbackPropName(currentPropName); - - if (ignoreCase) { - prevPropName = prevPropName.toLowerCase(); - currentPropName = currentPropName.toLowerCase(); - } +var sortPropTypes = require('./sort-prop-types'); +var isWarnedForDeprecation = false; - if (requiredFirst) { - if (previousIsRequired && !currentIsRequired) { - // Transition between required and non-required. Don't compare for alphabetical. - return curr; - } - if (!previousIsRequired && currentIsRequired) { - // Encountered a non-required prop after a required prop - context.report(curr, 'Required prop types must be listed before all other prop types'); - return curr; - } - } - - if (callbacksLast) { - if (!previousIsCallback && currentIsCallback) { - // Entering the callback prop section - return curr; - } - if (previousIsCallback && !currentIsCallback) { - // Encountered a non-callback prop after a callback prop - context.report(prev, 'Callback prop types must be listed after all other prop types'); - return prev; - } - } - - if (currentPropName < prevPropName) { - context.report(curr, 'Prop types declarations should be sorted alphabetically'); - return prev; - } - - return curr; - }, declarations[0]); - } - - return { - ClassProperty: function(node) { - if (isPropTypesDeclaration(node) && node.value && node.value.type === 'ObjectExpression') { - checkSorted(node.value.properties); - } - }, - - MemberExpression: function(node) { - if (isPropTypesDeclaration(node.property)) { - var right = node.parent.right; - if (right && right.type === 'ObjectExpression') { - checkSorted(right.properties); - } +module.exports = function(context) { + return Object.assign(sortPropTypes(context), { + Program: function() { + if (isWarnedForDeprecation || /\=-(f|-format)=/.test(process.argv.join('='))) { + return; } - }, - ObjectExpression: function(node) { - node.properties.forEach(function(property) { - if (!property.key) { - return; - } - if (!isPropTypesDeclaration(property.key)) { - return; - } - if (property.value.type === 'ObjectExpression') { - checkSorted(property.value.properties); - } - }); + /* eslint-disable no-console */ + console.log('The react/jsx-sort-prop-types rule is deprecated. Please ' + + 'use the react/sort-prop-types rule instead.'); + /* eslint-enable no-console */ + isWarnedForDeprecation = true; } - - }; + }); }; module.exports.schema = [{ diff --git a/lib/rules/sort-prop-types.js b/lib/rules/sort-prop-types.js new file mode 100644 index 0000000000..8dd0758acb --- /dev/null +++ b/lib/rules/sort-prop-types.js @@ -0,0 +1,154 @@ +/** + * @fileoverview Enforce propTypes declarations alphabetical sorting + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = function(context) { + + var configuration = context.options[0] || {}; + var requiredFirst = configuration.requiredFirst || false; + var callbacksLast = configuration.callbacksLast || false; + var ignoreCase = configuration.ignoreCase || false; + + /** + * Checks if node is `propTypes` declaration + * @param {ASTNode} node The AST node being checked. + * @returns {Boolean} True if node is `propTypes` declaration, false if not. + */ + function isPropTypesDeclaration(node) { + + // Special case for class properties + // (babel-eslint does not expose property name so we have to rely on tokens) + if (node.type === 'ClassProperty') { + var tokens = context.getFirstTokens(node, 2); + return (tokens[0] && tokens[0].value === 'propTypes') || + (tokens[1] && tokens[1].value === 'propTypes'); + } + + return Boolean( + node && + node.name === 'propTypes' + ); + } + + function getKey(node) { + return node.key.type === 'Identifier' ? node.key.name : node.key.value; + } + + function getValueName(node) { + return node.value.property && node.value.property.name; + } + + function isCallbackPropName(propName) { + return /^on[A-Z]/.test(propName); + } + + function isRequiredProp(node) { + return getValueName(node) === 'isRequired'; + } + + /** + * Checks if propTypes declarations are sorted + * @param {Array} declarations The array of AST nodes being checked. + * @returns {void} + */ + function checkSorted(declarations) { + declarations.reduce(function(prev, curr) { + var prevPropName = getKey(prev); + var currentPropName = getKey(curr); + var previousIsRequired = isRequiredProp(prev); + var currentIsRequired = isRequiredProp(curr); + var previousIsCallback = isCallbackPropName(prevPropName); + var currentIsCallback = isCallbackPropName(currentPropName); + + if (ignoreCase) { + prevPropName = prevPropName.toLowerCase(); + currentPropName = currentPropName.toLowerCase(); + } + + if (requiredFirst) { + if (previousIsRequired && !currentIsRequired) { + // Transition between required and non-required. Don't compare for alphabetical. + return curr; + } + if (!previousIsRequired && currentIsRequired) { + // Encountered a non-required prop after a required prop + context.report(curr, 'Required prop types must be listed before all other prop types'); + return curr; + } + } + + if (callbacksLast) { + if (!previousIsCallback && currentIsCallback) { + // Entering the callback prop section + return curr; + } + if (previousIsCallback && !currentIsCallback) { + // Encountered a non-callback prop after a callback prop + context.report(prev, 'Callback prop types must be listed after all other prop types'); + return prev; + } + } + + if (currentPropName < prevPropName) { + context.report(curr, 'Prop types declarations should be sorted alphabetically'); + return prev; + } + + return curr; + }, declarations[0]); + } + + return { + ClassProperty: function(node) { + if (isPropTypesDeclaration(node) && node.value && node.value.type === 'ObjectExpression') { + checkSorted(node.value.properties); + } + }, + + MemberExpression: function(node) { + if (isPropTypesDeclaration(node.property)) { + var right = node.parent.right; + if (right && right.type === 'ObjectExpression') { + checkSorted(right.properties); + } + } + }, + + ObjectExpression: function(node) { + node.properties.forEach(function(property) { + if (!property.key) { + return; + } + + if (!isPropTypesDeclaration(property.key)) { + return; + } + if (property.value.type === 'ObjectExpression') { + checkSorted(property.value.properties); + } + }); + } + + }; +}; + +module.exports.schema = [{ + type: 'object', + properties: { + requiredFirst: { + type: 'boolean' + }, + callbacksLast: { + type: 'boolean' + }, + ignoreCase: { + type: 'boolean' + } + }, + additionalProperties: false +}]; diff --git a/tests/lib/rules/jsx-sort-prop-types.js b/tests/lib/rules/jsx-sort-prop-types.js index b718dee589..5b87e39a81 100644 --- a/tests/lib/rules/jsx-sort-prop-types.js +++ b/tests/lib/rules/jsx-sort-prop-types.js @@ -1,5 +1,5 @@ /** - * @fileoverview Tests for jsx-sort-prop-types + * @fileoverview Tests for sort-prop-types */ 'use strict'; @@ -27,7 +27,7 @@ require('babel-eslint'); var ERROR_MESSAGE = 'Prop types declarations should be sorted alphabetically'; var ruleTester = new RuleTester(); -ruleTester.run('jsx-sort-prop-types', rule, { +ruleTester.run('sort-prop-types', rule, { valid: [{ code: [ diff --git a/tests/lib/rules/sort-prop-types.js b/tests/lib/rules/sort-prop-types.js new file mode 100644 index 0000000000..20450c7660 --- /dev/null +++ b/tests/lib/rules/sort-prop-types.js @@ -0,0 +1,599 @@ +/** + * @fileoverview Tests for sort-prop-types + */ +'use strict'; + +// ----------------------------------------------------------------------------- +// Requirements +// ----------------------------------------------------------------------------- + +var rule = require('../../../lib/rules/sort-prop-types'); +var RuleTester = require('eslint').RuleTester; + +var parserOptions = { + ecmaVersion: 6, + ecmaFeatures: { + experimentalObjectRestSpread: true, + jsx: true + } +}; + +require('babel-eslint'); + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +var ERROR_MESSAGE = 'Prop types declarations should be sorted alphabetically'; + +var ruleTester = new RuleTester(); +ruleTester.run('sort-prop-types', rule, { + + valid: [{ + code: [ + 'var First = React.createClass({', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + 'var First = React.createClass({', + ' propTypes: externalPropTypes,', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + 'var First = React.createClass({', + ' propTypes: {', + ' A: React.PropTypes.any,', + ' Z: React.PropTypes.string,', + ' a: React.PropTypes.any,', + ' z: React.PropTypes.string', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + 'var First = React.createClass({', + ' propTypes: {', + ' a: React.PropTypes.any,', + ' A: React.PropTypes.any,', + ' z: React.PropTypes.string,', + ' Z: React.PropTypes.string', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + options: [{ + ignoreCase: true + }], + parserOptions: parserOptions + }, { + code: [ + 'var First = React.createClass({', + ' propTypes: {', + ' a: React.PropTypes.any,', + ' z: React.PropTypes.string', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});', + 'var Second = React.createClass({', + ' propTypes: {', + ' AA: React.PropTypes.any,', + ' ZZ: React.PropTypes.string', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + 'class First extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'First.propTypes = {', + ' a: React.PropTypes.string,', + ' z: React.PropTypes.string', + '};', + 'First.propTypes.justforcheck = React.PropTypes.string;' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + 'class First extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'First.propTypes = {', + ' a: React.PropTypes.any,', + ' A: React.PropTypes.any,', + ' z: React.PropTypes.string,', + ' Z: React.PropTypes.string', + '};' + ].join('\n'), + options: [{ + ignoreCase: true + }], + parserOptions: parserOptions + }, { + code: [ + 'class Component extends React.Component {', + ' static propTypes = {', + ' a: React.PropTypes.any,', + ' b: React.PropTypes.any,', + ' c: React.PropTypes.any', + ' };', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + parserOptions: parserOptions + }, { + code: [ + 'class Hello extends React.Component {', + ' render() {', + ' return
Hello
;', + ' }', + '}', + 'Hello.propTypes = {', + ' "aria-controls": React.PropTypes.string', + '};' + ].join('\n'), + parser: 'babel-eslint', + options: [{ + ignoreCase: true + }] + }, { + // Invalid code, should not be validated + code: [ + 'class Component extends React.Component {', + ' propTypes: {', + ' a: React.PropTypes.any,', + ' c: React.PropTypes.any,', + ' b: React.PropTypes.any', + ' };', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint' + }, { + code: [ + 'var Hello = React.createClass({', + ' render: function() {', + ' let { a, ...b } = obj;', + ' let c = { ...d };', + ' return
;', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + 'var First = React.createClass({', + ' propTypes: {', + ' barRequired: React.PropTypes.func.isRequired,', + ' onBar: React.PropTypes.func,', + ' z: React.PropTypes.any', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + 'var First = React.createClass({', + ' propTypes: {', + ' a: React.PropTypes.any,', + ' z: React.PropTypes.string,', + ' onBar: React.PropTypes.func,', + ' onFoo: React.PropTypes.func', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + options: [{ + callbacksLast: true + }], + parserOptions: parserOptions + }, { + code: [ + 'class Component extends React.Component {', + ' static propTypes = {', + ' a: React.PropTypes.any,', + ' z: React.PropTypes.string,', + ' onBar: React.PropTypes.func,', + ' onFoo: React.PropTypes.func', + ' };', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + options: [{ + callbacksLast: true + }], + parser: 'babel-eslint', + parserOptions: parserOptions + }, { + code: [ + 'class First extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'First.propTypes = {', + ' a: React.PropTypes.any,', + ' z: React.PropTypes.string,', + ' onBar: React.PropTypes.func,', + ' onFoo: React.PropTypes.func', + '};' + ].join('\n'), + options: [{ + callbacksLast: true + }], + parserOptions: parserOptions + }, { + code: [ + 'class First extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'First.propTypes = {', + ' barRequired: React.PropTypes.string.isRequired,', + ' a: React.PropTypes.any', + '};' + ].join('\n'), + options: [{ + requiredFirst: true + }], + parserOptions: parserOptions + }, { + code: [ + 'class First extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'First.propTypes = {', + ' fooRequired: MyPropType,', + '};' + ].join('\n'), + options: [{ + requiredFirst: true + }], + parserOptions: parserOptions + }, { + code: [ + 'class First extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'First.propTypes = {', + ' barRequired: React.PropTypes.string.isRequired,', + ' fooRequired: React.PropTypes.any.isRequired,', + ' a: React.PropTypes.any,', + ' z: React.PropTypes.string,', + ' onBar: React.PropTypes.func,', + ' onFoo: React.PropTypes.func', + '};' + ].join('\n'), + options: [{ + requiredFirst: true, + callbacksLast: true + }], + parserOptions: parserOptions + }], + + invalid: [{ + code: [ + 'var First = React.createClass({', + ' propTypes: {', + ' z: React.PropTypes.string,', + ' a: React.PropTypes.any', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions, + errors: [{ + message: ERROR_MESSAGE, + line: 4, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'var First = React.createClass({', + ' propTypes: {', + ' z: React.PropTypes.any,', + ' Z: React.PropTypes.any', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions, + errors: [{ + message: ERROR_MESSAGE, + line: 4, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'var First = React.createClass({', + ' propTypes: {', + ' Z: React.PropTypes.any,', + ' a: React.PropTypes.any', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + options: [{ + ignoreCase: true + }], + parserOptions: parserOptions, + errors: [{ + message: ERROR_MESSAGE, + line: 4, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'var First = React.createClass({', + ' propTypes: {', + ' a: React.PropTypes.any,', + ' A: React.PropTypes.any,', + ' z: React.PropTypes.string,', + ' Z: React.PropTypes.string', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions, + errors: 2 + }, { + code: [ + 'var First = React.createClass({', + ' propTypes: {', + ' a: React.PropTypes.any,', + ' Zz: React.PropTypes.string', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});', + 'var Second = React.createClass({', + ' propTypes: {', + ' aAA: React.PropTypes.any,', + ' ZZ: React.PropTypes.string', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions, + errors: 2 + }, { + code: [ + 'class First extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'First.propTypes = {', + ' yy: React.PropTypes.any,', + ' bb: React.PropTypes.string', + '};', + 'class Second extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'Second.propTypes = {', + ' aAA: React.PropTypes.any,', + ' ZZ: React.PropTypes.string', + '};' + ].join('\n'), + parserOptions: parserOptions, + errors: 2 + }, { + code: [ + 'class Component extends React.Component {', + ' static propTypes = {', + ' z: React.PropTypes.any,', + ' y: React.PropTypes.any,', + ' a: React.PropTypes.any', + ' };', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + parserOptions: parserOptions, + errors: 2 + }, { + code: [ + 'var First = React.createClass({', + ' propTypes: {', + ' a: React.PropTypes.any,', + ' z: React.PropTypes.string,', + ' onFoo: React.PropTypes.func,', + ' onBar: React.PropTypes.func', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + options: [{ + callbacksLast: true + }], + parserOptions: parserOptions, + errors: [{ + message: ERROR_MESSAGE, + line: 6, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'class Component extends React.Component {', + ' static propTypes = {', + ' a: React.PropTypes.any,', + ' z: React.PropTypes.string,', + ' onFoo: React.PropTypes.func,', + ' onBar: React.PropTypes.func', + ' };', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + options: [{ + callbacksLast: true + }], + parser: 'babel-eslint', + parserOptions: parserOptions, + errors: [{ + message: ERROR_MESSAGE, + line: 6, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'class First extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'First.propTypes = {', + ' a: React.PropTypes.any,', + ' z: React.PropTypes.string,', + ' onFoo: React.PropTypes.func,', + ' onBar: React.PropTypes.func', + '};' + ].join('\n'), + options: [{ + callbacksLast: true + }], + parserOptions: parserOptions, + errors: [{ + message: ERROR_MESSAGE, + line: 10, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'var First = React.createClass({', + ' propTypes: {', + ' a: React.PropTypes.any,', + ' onBar: React.PropTypes.func,', + ' onFoo: React.PropTypes.func,', + ' z: React.PropTypes.string', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + options: [{ + callbacksLast: true + }], + parserOptions: parserOptions, + errors: [{ + message: 'Callback prop types must be listed after all other prop types', + line: 5, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'var First = React.createClass({', + ' propTypes: {', + ' fooRequired: React.PropTypes.string.isRequired,', + ' barRequired: React.PropTypes.string.isRequired,', + ' a: React.PropTypes.any', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + options: [{ + requiredFirst: true + }], + parserOptions: parserOptions, + errors: [{ + message: ERROR_MESSAGE, + line: 4, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'var First = React.createClass({', + ' propTypes: {', + ' a: React.PropTypes.any,', + ' barRequired: React.PropTypes.string.isRequired,', + ' onFoo: React.PropTypes.func', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + options: [{ + requiredFirst: true + }], + parserOptions: parserOptions, + errors: [{ + message: 'Required prop types must be listed before all other prop types', + line: 4, + column: 5, + type: 'Property' + }] + }] +});