Repository: yannickcr/eslint-plugin-react
Pull request #846 (2016-09-16 23:27:18)

Allow skipping prop-types validation when not declared
Adding the `prop-types` rule can be a daunting task for
projects that have not been consistent about using prop
types to begin with.

This allows users to slowly opt-in to prop-types validation
by only erroring on "incomplete" propTypes declarations.

pfhayes: @ljharb sure thing - let me know if this is what you had in mind

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4f69ac19..05901c1e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,26 @@ All notable changes to this project will be documented in this file.
 This project adheres to [Semantic Versioning](http://semver.org/).
 This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com).
 
+## [6.3.0] - 2016-09-20
+### Added
+* Add `no-children-prop` rule ([#720][] @benstepp)
+* Add `no-unescaped-entities` rule ([#681][] @pfhayes)
+* Add JSXExpressionContainer support to `jsx-indent` rule ([#838][] @eelyafi)
+
+### Fixed
+* Fix `style-prop-object` crash ([#834][])
+* Fix `style-prop-object` false positive on computed properties ([#820][])
+* Fix `style-prop-object` to deal with null and spread props that can't be resolved ([#809][] [#812][] @petersendidit)
+
+[6.3.0]: https://github.com/yannickcr/eslint-plugin-react/compare/v6.2.2...v6.3.0
+[#720]: https://github.com/yannickcr/eslint-plugin-react/issues/720
+[#681]: https://github.com/yannickcr/eslint-plugin-react/pull/681
+[#838]: https://github.com/yannickcr/eslint-plugin-react/pull/838
+[#834]: https://github.com/yannickcr/eslint-plugin-react/issues/834
+[#820]: https://github.com/yannickcr/eslint-plugin-react/issues/820
+[#809]: https://github.com/yannickcr/eslint-plugin-react/issues/809
+[#812]: https://github.com/yannickcr/eslint-plugin-react/issues/812
+
 ## [6.2.2] - 2016-09-15
 ### Fixed
 * Fix `no-unused-prop-types` crash ([#825][] @EvNaverniouk)
@@ -34,7 +54,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
 
 ## [6.2.0] - 2016-08-28
 ### Added
-* Adds `no-unused-prop-types` rule ([#226][] @EvNaverniouk)
+* Add `no-unused-prop-types` rule ([#226][] @EvNaverniouk)
 * Add `style-prop-object` rule ([#715][] @petersendidit)
 * Add auto fix for `self-closing-comp` ([#770][] @pl12133)
 * Add support for `typeAnnotations` in `sort-comp` ([#235][] @dozoisch)
diff --git a/README.md b/README.md
index 3ffce7c8..dd63ac0d 100644
--- a/README.md
+++ b/README.md
@@ -167,12 +167,12 @@ The rules enabled in this configuration are:
 * [react/jsx-no-undef](docs/rules/jsx-no-undef.md)
 * [react/jsx-uses-react](docs/rules/jsx-uses-react.md)
 * [react/jsx-uses-vars](docs/rules/jsx-uses-vars.md)
-* [react/no-danger](docs/rules/no-danger.md)
 * [react/no-deprecated](docs/rules/no-deprecated.md)
 * [react/no-direct-mutation-state](docs/rules/no-direct-mutation-state.md)
 * [react/no-find-dom-node](docs/rules/no-find-dom-node.md)
 * [react/no-is-mounted](docs/rules/no-is-mounted.md)
 * [react/no-unknown-property](docs/rules/no-unknown-property.md)
+* [react/no-render-return-value](docs/rules/no-render-return-value.md)
 * [react/prop-types](docs/rules/prop-types.md)
 * [react/react-in-jsx-scope](docs/rules/react-in-jsx-scope.md)
 * [react/require-render-return](docs/rules/require-render-return.md)
diff --git a/docs/rules/jsx-indent.md b/docs/rules/jsx-indent.md
index 5f62102d..cea6faae 100644
--- a/docs/rules/jsx-indent.md
+++ b/docs/rules/jsx-indent.md
@@ -2,6 +2,9 @@
 
 This option validates a specific indentation style for JSX.
 
+**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line. 
+Fixer will fix whitespace and tabs indentation. It won't replace tabs with whitespaces and vice-versa.
+
 ## Rule Details
 
 This rule is aimed to enforce consistent indentation style. The default style is `4 spaces`.
diff --git a/docs/rules/prefer-stateless-function.md b/docs/rules/prefer-stateless-function.md
index ddaab95d..3efee0ca 100644
--- a/docs/rules/prefer-stateless-function.md
+++ b/docs/rules/prefer-stateless-function.md
@@ -1,6 +1,6 @@
 # Enforce stateless React Components to be written as a pure function (prefer-stateless-function)
 
-Stateless functional components are more simple than class based components and will benefit from future React performance optimizations specific to these components.
+Stateless functional components are simpler than class based components and will benefit from future React performance optimizations specific to these components.
 
 ## Rule Details
 
diff --git a/docs/rules/prop-types.md b/docs/rules/prop-types.md
index c705a30d..839c6094 100644
--- a/docs/rules/prop-types.md
+++ b/docs/rules/prop-types.md
@@ -109,6 +109,7 @@ This rule can take one argument to ignore some specific props during validation.
 * `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0.
 * `ignore`: optional array of props name to ignore during validation.
 * `customValidators`: optional array of validators used for propTypes validation.
+* `skipUndeclared`: only error on components that have a propTypes block declared
 
 ### As for "exceptions"
 
diff --git a/lib/rules/display-name.js b/lib/rules/display-name.js
index 1e68f6b7..00e8a34b 100644
--- a/lib/rules/display-name.js
+++ b/lib/rules/display-name.js
@@ -125,7 +125,8 @@ module.exports = {
       var namedFunctionExpression = (
         (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') &&
         node.parent &&
-        (node.parent.type === 'VariableDeclarator' || node.parent.method === true)
+        (node.parent.type === 'VariableDeclarator' || node.parent.method === true) &&
+        (!node.parent.parent || !utils.isES5Component(node.parent.parent))
       );
 
       if (
diff --git a/lib/rules/jsx-indent.js b/lib/rules/jsx-indent.js
index dc4d582a..8590a99b 100644
--- a/lib/rules/jsx-indent.js
+++ b/lib/rules/jsx-indent.js
@@ -39,7 +39,9 @@ module.exports = {
       category: 'Stylistic Issues',
       recommended: false
     },
-
+    // fixer will fix whitespace and tabs indentation.
+    // It won't replace tabs with whitespaces and vice-versa.
+    fixable: 'whitespace',
     schema: [{
       oneOf: [{
         enum: ['tab']
@@ -69,6 +71,30 @@ module.exports = {
       }
     }
 
+    var indentChar = indentType === 'space' ? ' ' : '\t';
+
+    /**
+     * Responsible for fixing the indentation issue fix
+     * @param {ASTNode} node Node violating the indent rule
+     * @param {Number} needed Expected indentation character count
+     * @param {Number} gotten Indentation character count in the actual node/code
+     * @returns {Function} function to be executed by the fixer
+     * @private
+     */
+    function getFixerFunction(node, needed, gotten) {
+      if (needed > gotten) {
+        var spaces = indentChar.repeat(needed - gotten);
+
+        return function(fixer) {
+          return fixer.insertTextBeforeRange([node.range[0], node.range[0]], spaces);
+        };
+      }
+
+      return function(fixer) {
+        return fixer.removeRange([node.range[0] - (gotten - needed), node.range[0]]);
+      };
+    }
+
     /**
      * Reports a given indent violation and properly pluralizes the message
      * @param {ASTNode} node Node violating the indent rule
@@ -89,13 +115,15 @@ module.exports = {
           node: node,
           loc: loc,
           message: MESSAGE,
-          data: msgContext
+          data: msgContext,
+          fix: getFixerFunction(node, needed, gotten)
         });
       } else {
         context.report({
           node: node,
           message: MESSAGE,
-          data: msgContext
+          data: msgContext,
+          fix: getFixerFunction(node, needed, gotten)
         });
       }
     }
diff --git a/lib/rules/no-children-prop.js b/lib/rules/no-children-prop.js
index 31d1b965..54540db7 100644
--- a/lib/rules/no-children-prop.js
+++ b/lib/rules/no-children-prop.js
@@ -54,7 +54,7 @@ module.exports = {
 
         var props = node.arguments[1].properties;
         var childrenProp = props.find(function(prop) {
-          return prop.key.name === 'children';
+          return prop.key && prop.key.name === 'children';
         });
 
         if (childrenProp) {
diff --git a/lib/rules/no-unused-prop-types.js b/lib/rules/no-unused-prop-types.js
index 4c2c146c..8b8f958d 100644
--- a/lib/rules/no-unused-prop-types.js
+++ b/lib/rules/no-unused-prop-types.js
@@ -605,6 +605,7 @@ module.exports = {
 
       var component = components.get(utils.getParentComponent());
       var usedPropTypes = component && component.usedPropTypes || [];
+      var ignorePropsValidation = component && component.ignorePropsValidation || false;
 
       switch (type) {
         case 'direct':
@@ -621,7 +622,8 @@ module.exports = {
         case 'destructuring':
           for (var k = 0, l = (properties || []).length; k < l; k++) {
             if (hasSpreadOperator(properties[k]) || properties[k].computed) {
-              continue;
+              ignorePropsValidation = true;
+              break;
             }
             var propName = getKeyValue(properties[k]);
 
@@ -646,7 +648,8 @@ module.exports = {
       }
 
       components.set(node, {
-        usedPropTypes: usedPropTypes
+        usedPropTypes: usedPropTypes,
+        ignorePropsValidation: ignorePropsValidation
       });
     }
 
@@ -658,7 +661,7 @@ module.exports = {
     function markPropTypesAsDeclared(node, propTypes) {
       var component = components.get(node);
       var declaredPropTypes = component && component.declaredPropTypes || [];
-      var ignorePropsValidation = false;
+      var ignorePropsValidation = component && component.ignorePropsValidation || false;
 
       switch (propTypes && propTypes.type) {
         case 'ObjectTypeAnnotation':
@@ -936,4 +939,3 @@ module.exports = {
     };
   })
 };
-
diff --git a/lib/rules/prefer-stateless-function.js b/lib/rules/prefer-stateless-function.js
index e4f2ecc9..540c7170 100644
--- a/lib/rules/prefer-stateless-function.js
+++ b/lib/rules/prefer-stateless-function.js
@@ -235,6 +235,16 @@ module.exports = {
       });
     };
 
+    /**
+     * Mark childContextTypes as declared
+     * @param {ASTNode} node The AST node being checked.
+     */
+    var markChildContextTypesAsDeclared = function (node) {
+      components.set(node, {
+        hasChildContextTypes: true
+      });
+    };
+
     /**
      * Mark a setState as used
      * @param {ASTNode} node The AST node being checked.
@@ -302,9 +312,17 @@ module.exports = {
 
       // Mark `this` usage
       MemberExpression: function(node) {
-        // Ignore calls to `this.props` and `this.context`
         if (node.object.type !== 'ThisExpression') {
+          if (node.property && node.property.name === 'childContextTypes') {
+            var component = utils.getRelatedComponent(node);
+            if (!component) {
+              return;
+            }
+            markChildContextTypesAsDeclared(component.node);
+            return;
+          }
           return;
+        // Ignore calls to `this.props` and `this.context`
         } else if (
           (node.property.name || node.property.value) === 'props' ||
           (node.property.name || node.property.value) === 'context'
@@ -358,6 +376,7 @@ module.exports = {
             list[component].useThis ||
             list[component].useRef ||
             list[component].invalidReturn ||
+            list[component].hasChildContextTypes ||
             (!utils.isES5Component(list[component].node) && !utils.isES6Component(list[component].node))
           ) {
             continue;
diff --git a/lib/rules/prop-types.js b/lib/rules/prop-types.js
index 612ba943..d452df66 100644
--- a/lib/rules/prop-types.js
+++ b/lib/rules/prop-types.js
@@ -42,6 +42,9 @@ module.exports = {
           items: {
             type: 'string'
           }
+        },
+        skipUndeclared: {
+          type: 'boolean'
         }
       },
       additionalProperties: false
@@ -54,6 +57,7 @@ module.exports = {
     var configuration = context.options[0] || {};
     var ignored = configuration.ignore || [];
     var customValidators = configuration.customValidators || [];
+    var skipUndeclared = configuration.skipUndeclared || false;
     // Used to track the type annotations in scope.
     // Necessary because babel's scopes do not track type annotations.
     var stack = null;
@@ -134,7 +138,6 @@ module.exports = {
      * @returns {Boolean} True if we are declaring a prop, 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 && node.type === 'ClassProperty') {
@@ -179,10 +182,12 @@ module.exports = {
      * @returns {Boolean} True if the component must be validated, false if not.
      */
     function mustBeValidated(component) {
+      var isSkippedByConfig = skipUndeclared && typeof component.declaredPropTypes === 'undefined';
       return Boolean(
         component &&
         component.usedPropTypes &&
-        !component.ignorePropsValidation
+        !component.ignorePropsValidation &&
+        !isSkippedByConfig
       );
     }
 
diff --git a/lib/util/Components.js b/lib/util/Components.js
index c9c6e10a..181dfa24 100644
--- a/lib/util/Components.js
+++ b/lib/util/Components.js
@@ -275,7 +275,7 @@ function componentRule(rule, context) {
      * @param {ASTNode} ASTnode The AST node being checked
      */
     findReturnStatement: function(node) {
-      if (!node.value || !node.value.body) {
+      if (!node.value || !node.value.body || !node.value.body.body) {
         return false;
       }
       var i = node.value.body.body.length - 1;
diff --git a/package.json b/package.json
index b4fa6c46..366b8476 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint-plugin-react",
-  "version": "6.2.2",
+  "version": "6.3.0",
   "author": "Yannick Croissant <yannick.croissant+npm@gmail.com>",
   "description": "React specific linting rules for ESLint",
   "main": "index.js",


Test patch:
diff --git a/tests/lib/rules/display-name.js b/tests/lib/rules/display-name.js
index 083cee78..3838c4c6 100644
--- a/tests/lib/rules/display-name.js
+++ b/tests/lib/rules/display-name.js
@@ -464,6 +464,18 @@ ruleTester.run('display-name', rule, {
     errors: [{
       message: 'Component definition is missing display name'
     }]
+  }, {
+    code: [
+      'module.exports = React.createClass({',
+      '  render() {',
+      '    return <div>Hello {this.props.name}</div>;',
+      '  }',
+      '});'
+    ].join('\n'),
+    parser: 'babel-eslint',
+    errors: [{
+      message: 'Component definition is missing display name'
+    }]
   }, {
     code: [
       'var Hello = React.createClass({',
diff --git a/tests/lib/rules/jsx-indent.js b/tests/lib/rules/jsx-indent.js
index 3eb7ba9d..b70f3fe1 100644
--- a/tests/lib/rules/jsx-indent.js
+++ b/tests/lib/rules/jsx-indent.js
@@ -107,6 +107,11 @@ ruleTester.run('jsx-indent', rule, {
       '  <Foo />',
       '</App>'
     ].join('\n'),
+    output: [
+      '<App>',
+      '    <Foo />',
+      '</App>'
+    ].join('\n'),
     parserOptions: parserOptions,
     errors: [{message: 'Expected indentation of 4 space characters but found 2.'}]
   }, {
@@ -115,6 +120,11 @@ ruleTester.run('jsx-indent', rule, {
       '    <Foo />',
       '</App>'
     ].join('\n'),
+    output: [
+      '<App>',
+      '  <Foo />',
+      '</App>'
+    ].join('\n'),
     options: [2],
     parserOptions: parserOptions,
     errors: [{message: 'Expected indentation of 2 space characters but found 4.'}]
@@ -135,6 +145,13 @@ ruleTester.run('jsx-indent', rule, {
       '         </App>;',
       '}'
     ].join('\n'),
+    output: [
+      'function App() {',
+      '  return <App>',
+      '    <Foo />',
+      '  </App>;',
+      '}'
+    ].join('\n'),
     options: [2],
     parserOptions: parserOptions,
     errors: [{message: 'Expected indentation of 2 space characters but found 9.'}]
@@ -146,6 +163,13 @@ ruleTester.run('jsx-indent', rule, {
       '    </App>);',
       '}'
     ].join('\n'),
+    output: [
+      'function App() {',
+      '  return (<App>',
+      '    <Foo />',
+      '  </App>);',
+      '}'
+    ].join('\n'),
     options: [2],
     parserOptions: parserOptions,
     errors: [{message: 'Expected indentation of 2 space characters but found 4.'}]
@@ -212,6 +236,57 @@ ruleTester.run('jsx-indent', rule, {
     errors: [
       {message: 'Expected indentation of 3 tab characters but found 2.'}
     ]
-  }]
+  }, {
+    code: [
+      '<App>\n',
+      '<Foo />\n',
+      '</App>'
+    ].join('\n'),
+    output: [
+      '<App>\n',
+      '\t<Foo />\n',
+      '</App>'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    options: ['tab'],
+    errors: [
+      {message: 'Expected indentation of 1 tab character but found 0.'}
+    ]
+  }
+  // Tests for future work. See the comment on line 42-43 in the rule near to fixable: 'whitespace' meta property,
+  // Right now fixer function doesn't support replacing tabs with whitespaces and vice-versa.
+  /* , {
+    code: [
+      '<App>\n',
+      ' <Foo />\n',
+      '</App>'
+    ].join('\n'),
+    output: [
+      '<App>\n',
+      '\t<Foo />\n',
+      '</App>'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    options: ['tab'],
+    errors: [
+      {message: 'Expected indentation of 1 tab character but found 0.'}
+    ]
+  }, {
+    code: [
+      '<App>\n',
+      '\t<Foo />\n',
+      '</App>'
+    ].join('\n'),
+    output: [
+      '<App>\n',
+      '  <Foo />\n',
+      '</App>'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    options: [2],
+    errors: [
+      {message: 'Expected indentation of 2 space characters but found 0.'}
+    ]
+  }*/]
 });
 
diff --git a/tests/lib/rules/jsx-no-bind.js b/tests/lib/rules/jsx-no-bind.js
index 02362089..8b807277 100644
--- a/tests/lib/rules/jsx-no-bind.js
+++ b/tests/lib/rules/jsx-no-bind.js
@@ -167,6 +167,17 @@ ruleTester.run('jsx-no-bind', rule, {
       errors: [{message: 'JSX props should not use .bind()'}],
       parser: 'babel-eslint'
     },
+    {
+      code: [
+        'const foo = {',
+        '  render: ({onClick}) => (',
+        '    <div onClick={onClick.bind(this)}>Hello</div>',
+        '  )',
+        '};'
+      ].join('\n'),
+      errors: [{message: 'JSX props should not use .bind()'}],
+      parser: 'babel-eslint'
+    },
     {
       code: [
         'const foo = {',
diff --git a/tests/lib/rules/no-children-prop.js b/tests/lib/rules/no-children-prop.js
index d0073dc0..dbcb684d 100644
--- a/tests/lib/rules/no-children-prop.js
+++ b/tests/lib/rules/no-children-prop.js
@@ -15,6 +15,7 @@ var RuleTester = require('eslint').RuleTester;
 var parserOptions = {
   ecmaVersion: 6,
   ecmaFeatures: {
+    experimentalObjectRestSpread: true,
     jsx: true
   }
 };
@@ -170,6 +171,14 @@ ruleTester.run('no-children-prop', rule, {
     {
       code: 'React.createElement(MyComponent, {className: "class-name"}, "Children");',
       parserOptions: parserOptions
+    },
+    {
+      code: '<MyComponent className="class-name" {...props} />;',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement(MyComponent, {className: "class-name", ...props});',
+      parserOptions: parserOptions
     }
   ],
   invalid: [
@@ -232,6 +241,16 @@ ruleTester.run('no-children-prop', rule, {
       code: 'React.createElement(MyComponent, {children: "Children", className: "class-name"});',
       errors: [{message: CREATE_ELEMENT_ERROR}],
       parserOptions: parserOptions
+    },
+    {
+      code: '<MyComponent {...props} children="Children" />;',
+      errors: [{message: JSX_ERROR}],
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement(MyComponent, {...props, children: "Children"})',
+      errors: [{message: CREATE_ELEMENT_ERROR}],
+      parserOptions: parserOptions
     }
   ]
 });
diff --git a/tests/lib/rules/no-unused-prop-types.js b/tests/lib/rules/no-unused-prop-types.js
index c703f425..8c273278 100644
--- a/tests/lib/rules/no-unused-prop-types.js
+++ b/tests/lib/rules/no-unused-prop-types.js
@@ -177,15 +177,19 @@ ruleTester.run('no-unused-prop-types', rule, {
       ].join('\n'),
       parserOptions: parserOptions
     }, {
+      // Props validation is ignored when spread is used
       code: [
         'class Hello extends React.Component {',
         '  render() {',
-        '    var { firstname, ...other } = this.props;',
+        '    var { firstname, ...props } = this.props;',
+        '    var { category, icon } = props;',
         '    return <div>Hello {firstname}</div>;',
         '  }',
         '}',
         'Hello.propTypes = {',
-        '  firstname: React.PropTypes.string',
+        '  firstname: React.PropTypes.string,',
+        '  category: React.PropTypes.string,',
+        '  icon: React.PropTypes.bool',
         '};'
       ].join('\n'),
       parser: 'babel-eslint',
@@ -1653,25 +1657,6 @@ ruleTester.run('no-unused-prop-types', rule, {
         {message: '\'a.unused\' PropType is defined but prop is never used'},
         {message: '\'a.anotherunused\' PropType is defined but prop is never used'}
       ]
-    }, {
-      code: [
-        'class Hello extends React.Component {',
-        '  render() {',
-        '    var { ',
-        '      "aria-controls": ariaControls, ',
-        '      propX,',
-        '      ...props } = this.props;',
-        '    return <div>Hello</div>;',
-        '  }',
-        '}',
-        'Hello.propTypes = {',
-        '  "aria-unused": React.PropTypes.string',
-        '};'
-      ].join('\n'),
-      parser: 'babel-eslint',
-      errors: [
-        {message: '\'aria-unused\' PropType is defined but prop is never used'}
-      ]
     }, {
       code: [
         'class Hello extends React.Component {',
diff --git a/tests/lib/rules/prefer-stateless-function.js b/tests/lib/rules/prefer-stateless-function.js
index d2f945b5..34e46016 100644
--- a/tests/lib/rules/prefer-stateless-function.js
+++ b/tests/lib/rules/prefer-stateless-function.js
@@ -255,6 +255,19 @@ ruleTester.run('prefer-stateless-function', rule, {
         ');'
       ].join('\n'),
       parser: 'babel-eslint'
+    }, {
+      // Has childContextTypes
+      code: [
+        'class Foo extends React.Component {',
+        '  render() {',
+        '    return <div>{this.props.children}</div>;',
+        '  }',
+        '}',
+        'Foo.childContextTypes = {',
+        '  color: React.PropTypes.string',
+        '};'
+      ].join('\n'),
+      parser: 'babel-eslint'
     }
   ],
 
diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js
index 511e8455..3a60db4a 100644
--- a/tests/lib/rules/prop-types.js
+++ b/tests/lib/rules/prop-types.js
@@ -1305,6 +1305,62 @@ ruleTester.run('prop-types', rule, {
         '}'
       ].join('\n'),
       parser: 'babel-eslint'
+    }, {
+      code: [
+        'var Hello = React.createClass({',
+        '  render: function() {',
+        '    return <div>{this.props.name}</div>;',
+        '  }',
+        '});'
+      ].join('\n'),
+      options: [{skipUndeclared: true}],
+      parserOptions: parserOptions
+    }, {
+      code: [
+        'var Hello = React.createClass({',
+        '  render: function() {',
+        '    return <div>{this.props.name}</div>;',
+        '  }',
+        '});'
+      ].join('\n'),
+      options: [{skipUndeclared: true}],
+      parserOptions: parserOptions
+    }, {
+      code: [
+        'class Hello extends React.Component {',
+        '  render() {',
+        '    return <div>{this.props.name}</div>;',
+        '  }',
+        '}'
+      ].join('\n'),
+      options: [{skipUndeclared: true}],
+      parserOptions: parserOptions
+    }, {
+      code: [
+        'var Hello = React.createClass({',
+        '  propTypes: {',
+        '    name: React.PropTypes.object.isRequired',
+        '  },',
+        '  render: function() {',
+        '    return <div>{this.props.name}</div>;',
+        '  }',
+        '});'
+      ].join('\n'),
+      options: [{skipUndeclared: true}],
+      parserOptions: parserOptions
+    }, {
+      code: [
+        'var Hello = React.createClass({',
+        '  propTypes: {',
+        '    name: React.PropTypes.object.isRequired',
+        '  },',
+        '  render: function() {',
+        '    return <div>{this.props.name}</div>;',
+        '  }',
+        '});'
+      ].join('\n'),
+      options: [{skipUndeclared: false}],
+      parserOptions: parserOptions
     }
   ],
 
@@ -2312,6 +2368,85 @@ ruleTester.run('prop-types', rule, {
         column: 27,
         type: 'Property'
       }]
+    }, {
+      code: [
+        'var Hello = React.createClass({',
+        '  propTypes: {},',
+        '  render: function() {',
+        '    return <div>{this.props.firstname}</div>;',
+        '  }',
+        '});'
+      ].join('\n'),
+      options: [{skipUndeclared: true}],
+      parserOptions: parserOptions,
+      errors: [{
+        message: '\'firstname\' is missing in props validation',
+        line: 4,
+        column: 29
+      }]
+    }, {
+      code: [
+        'var Hello = function(props) {',
+        '  return <div>{props.firstname}</div>;',
+        '};',
+        'Hello.propTypes = {}'
+      ].join('\n'),
+      options: [{skipUndeclared: true}],
+      parserOptions: parserOptions,
+      errors: [{
+        message: '\'firstname\' is missing in props validation',
+        line: 2,
+        column: 22
+      }]
+    }, {
+      code: [
+        'class Hello extends React.Component {',
+        '  static get propTypes() {',
+        '    return {};',
+        '  }',
+        '  render() {',
+        '    return <div>{this.props.firstname}</div>;',
+        '  }',
+        '}'
+      ].join('\n'),
+      options: [{skipUndeclared: true}],
+      parserOptions: parserOptions,
+      errors: [{
+        message: '\'firstname\' is missing in props validation',
+        line: 6,
+        column: 29
+      }]
+    }, {
+      code: [
+        'class Hello extends React.Component {',
+        '  render() {',
+        '    return <div>{this.props.firstname}</div>;',
+        '  }',
+        '}',
+        'Hello.propTypes = {};'
+      ].join('\n'),
+      options: [{skipUndeclared: true}],
+      parserOptions: parserOptions,
+      errors: [{
+        message: '\'firstname\' is missing in props validation',
+        line: 3,
+        column: 29
+      }]
+    }, {
+      code: [
+        'var Hello = React.createClass({',
+        '  render: function() {',
+        '    return <div>{this.props.firstname}</div>;',
+        '  }',
+        '});'
+      ].join('\n'),
+      options: [{skipUndeclared: false}],
+      parserOptions: parserOptions,
+      errors: [{
+        message: '\'firstname\' is missing in props validation',
+        line: 3,
+        column: 29
+      }]
     }
   ]
 });