Repository: yannickcr/eslint-plugin-react
Pull request #838 (2016-09-15 22:29:07)

Add JSXExpressionContainer support for jsx-indent rule
Right now jsx-indent linter won't catch cases like:
```
<App>
   {test}
</App>
```
This diff will add support for JSXExpressionContainer


Patch:
diff --git a/README.md b/README.md
index a696ff08..3ffce7c8 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,7 @@ Finally, enable all of the rules that you would like to use.  Use [our preset](#
 * [react/display-name](docs/rules/display-name.md): Prevent missing `displayName` in a React component definition
 * [react/forbid-component-props](docs/rules/forbid-component-props.md): Forbid certain props on Components
 * [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes
+* [react/no-children-prop](docs/rules/no-children-prop.md): Prevent passing children as props
 * [react/no-danger](docs/rules/no-danger.md): Prevent usage of dangerous JSX properties
 * [react/no-danger-with-children](docs/rules/no-danger-with-children.md): Prevent problem with children and props.dangerouslySetInnerHTML
 * [react/no-deprecated](docs/rules/no-deprecated.md): Prevent usage of deprecated methods
@@ -95,6 +96,7 @@ Finally, enable all of the rules that you would like to use.  Use [our preset](#
 * [react/no-render-return-value](docs/rules/no-render-return-value.md): Prevent usage of the return value of `React.render`
 * [react/no-set-state](docs/rules/no-set-state.md): Prevent usage of `setState`
 * [react/no-string-refs](docs/rules/no-string-refs.md): Prevent using string references in `ref` attribute.
+* [react/no-unescaped-entities](docs/rules/no-unescaped-entities.md): Prevent invalid characters from appearing in markup
 * [react/no-unknown-property](docs/rules/no-unknown-property.md): Prevent usage of unknown DOM property (fixable)
 * [react/no-unused-prop-types](docs/rules/no-unused-prop-types.md): Prevent definitions of unused prop types
 * [react/prefer-es6-class](docs/rules/prefer-es6-class.md): Enforce ES5 or ES6 class for React Components
@@ -165,6 +167,7 @@ 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)
diff --git a/docs/rules/no-children-prop.md b/docs/rules/no-children-prop.md
new file mode 100644
index 00000000..e49c7d87
--- /dev/null
+++ b/docs/rules/no-children-prop.md
@@ -0,0 +1,36 @@
+# Prevent passing of children as props (no-children-prop)
+
+Children should always be actual children, not passed in as a prop.
+
+When using JSX, the children should be nested between the opening and closing
+tags. When not using JSX, the children should be passed as additional
+arguments to `React.createElement`.
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```jsx
+<div children='Children' />
+
+<MyComponent children={<AnotherComponent />} />
+<MyComponent children={['Child 1', 'Child 2']} />
+
+React.createElement("div", { children: 'Children' })
+```
+
+The following patterns are not considered warnings:
+
+```jsx
+<div>Children</div>
+
+<MyComponent>Children</MyComponent>
+
+<MyComponent>
+  <span>Child 1</span>
+  <span>Child 2</span>
+</MyComponent>
+
+React.createElement("div", {}, 'Children')
+React.createElement("div", 'Child 1', 'Child 2')
+```
diff --git a/docs/rules/no-unescaped-entities.md b/docs/rules/no-unescaped-entities.md
new file mode 100644
index 00000000..2040d2c0
--- /dev/null
+++ b/docs/rules/no-unescaped-entities.md
@@ -0,0 +1,70 @@
+# Prevent invalid characters from appearing in markup (no-unescaped-entities)
+
+This rule prevents characters that you may have meant as JSX escape characters 
+from being accidentally injected as a text node in JSX statements.
+
+For example, if one were to misplace their closing `>` in a tag:
+
+```jsx
+<MyComponent
+  name="name"
+  type="string"
+  foo="bar">  {/* oops! */}
+  x="y">
+  Body Text
+</MyComponent>
+```
+
+The body text of this would render as `x="y"> Body Text`, which is probably not
+what was intended. This rule requires that these special characters are
+escaped if they appear in the body of a tag.
+
+Another example is when one accidentally includes an extra closing brace.
+
+```jsx
+<MyComponent>{'Text'}}</MyComponent>
+```
+
+The extra brace will be rendered, and the body text will be `Text}`.
+
+This rule will also check for `"` and `'`, which might be accidentally included 
+when the closing `>` is in the wrong place.
+
+```jsx
+<MyComponent
+  a="b">  {/* oops! */}
+  c="d"
+  Intended body text
+</MyComponent>
+```
+
+The preferred way to include one of these characters is to use the HTML escape code.
+
+- `>` can be replaced with `&gt;`
+- `"` can be replaced with `&quot;`, `&ldquo;` or `&rdquo;`
+- `'` can be replaced with `&apos;`, `&lsquo;` or `&rsquo;`
+- `}` can be replaced with `&#125;`
+
+Alternatively, you can include the literal character inside a subexpression
+(such as `<div>{'>'}</div>`.
+
+The characters `<` and `{` should also be escaped, but they are not checked by this
+rule because it is a syntax error to include those tokens inside of a tag.
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```jsx
+<div> > </div>
+```
+
+The following patterns are not considered warnings:
+
+```jsx
+<div> &gt; </div>
+```
+
+```jsx
+<div> {'>'} </div>
+```
diff --git a/index.js b/index.js
index a0ac8bf0..6bc26713 100644
--- a/index.js
+++ b/index.js
@@ -21,6 +21,7 @@ var rules = {
   'no-did-mount-set-state': require('./lib/rules/no-did-mount-set-state'),
   'no-did-update-set-state': require('./lib/rules/no-did-update-set-state'),
   'no-render-return-value': require('./lib/rules/no-render-return-value'),
+  'no-unescaped-entities': require('./lib/rules/no-unescaped-entities'),
   'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'),
   'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'),
   'jsx-handler-names': require('./lib/rules/jsx-handler-names'),
@@ -56,7 +57,8 @@ var rules = {
   'no-find-dom-node': require('./lib/rules/no-find-dom-node'),
   'no-danger-with-children': require('./lib/rules/no-danger-with-children'),
   'style-prop-object': require('./lib/rules/style-prop-object'),
-  'no-unused-prop-types': require('./lib/rules/no-unused-prop-types')
+  'no-unused-prop-types': require('./lib/rules/no-unused-prop-types'),
+  'no-children-prop': require('./lib/rules/no-children-prop')
 };
 
 var ruleNames = Object.keys(rules);
diff --git a/lib/rules/jsx-indent.js b/lib/rules/jsx-indent.js
index 62452be4..dc4d582a 100644
--- a/lib/rules/jsx-indent.js
+++ b/lib/rules/jsx-indent.js
@@ -176,6 +176,13 @@ module.exports = {
         }
         var peerElementIndent = getNodeIndent(node.parent.openingElement);
         checkNodesIndent(node, peerElementIndent);
+      },
+      JSXExpressionContainer: function(node) {
+        if (!node.parent) {
+          return;
+        }
+        var parentNodeIndent = getNodeIndent(node.parent);
+        checkNodesIndent(node, parentNodeIndent + indentSize);
       }
     };
 
diff --git a/lib/rules/no-children-prop.js b/lib/rules/no-children-prop.js
new file mode 100644
index 00000000..31d1b965
--- /dev/null
+++ b/lib/rules/no-children-prop.js
@@ -0,0 +1,69 @@
+/**
+ * @fileoverview Prevent passing of children as props
+ * @author Benjamin Stepp
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+
+/**
+ * Checks if the node is a createElement call with a props literal.
+ * @param {ASTNode} node - The AST node being checked.
+ * @returns {Boolean} - True if node is a createElement call with a props
+ * object literal, False if not.
+*/
+function isCreateElementWithProps(node) {
+  return node.callee
+    && node.callee.type === 'MemberExpression'
+    && node.callee.property.name === 'createElement'
+    && node.arguments.length > 1
+    && node.arguments[1].type === 'ObjectExpression';
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Prevent passing of children as props.',
+      category: 'Best Practices',
+      recommended: false
+    },
+    schema: []
+  },
+  create: function(context) {
+    return {
+      JSXAttribute: function(node) {
+        if (node.name.name !== 'children') {
+          return;
+        }
+
+        context.report({
+          node: node,
+          message: 'Do not pass children as props. Instead, nest children between the opening and closing tags.'
+        });
+      },
+      CallExpression: function(node) {
+        if (!isCreateElementWithProps(node)) {
+          return;
+        }
+
+        var props = node.arguments[1].properties;
+        var childrenProp = props.find(function(prop) {
+          return prop.key.name === 'children';
+        });
+
+        if (childrenProp) {
+          context.report({
+            node: node,
+            message: 'Do not pass children as props. Instead, pass them as additional arguments to React.createElement.'
+          });
+        }
+      }
+    };
+  }
+};
diff --git a/lib/rules/no-unescaped-entities.js b/lib/rules/no-unescaped-entities.js
new file mode 100644
index 00000000..3548cb43
--- /dev/null
+++ b/lib/rules/no-unescaped-entities.js
@@ -0,0 +1,80 @@
+/**
+ * @fileoverview HTML special characters should be escaped.
+ * @author Patrick Hayes
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+// NOTE: '<' and '{' are also problematic characters, but they do not need
+// to be included here because it is a syntax error when these characters are
+// included accidentally.
+var DEFAULTS = ['>', '"', '\'', '}'];
+
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Detect unescaped HTML entities, which might represent malformed tags',
+      category: 'Possible Errors',
+      recommended: false
+    },
+    schema: [{
+      type: 'object',
+      properties: {
+        forbid: {
+          type: 'array',
+          items: {
+            type: 'string'
+          }
+        }
+      },
+      additionalProperties: false
+    }]
+  },
+
+  create: function(context) {
+    function isInvalidEntity(node) {
+      var configuration = context.options[0] || {};
+      var entities = configuration.forbid || DEFAULTS;
+
+      // HTML entites are already escaped in node.value (as well as node.raw),
+      // so pull the raw text from context.getSourceCode()
+      for (var i = node.loc.start.line; i <= node.loc.end.line; i++) {
+        var rawLine = context.getSourceCode().lines[i - 1];
+        var start = 0;
+        var end = rawLine.length;
+        if (i === node.loc.start.line) {
+          start = node.loc.start.column;
+        }
+        if (i === node.loc.end.line) {
+          end = node.loc.end.column;
+        }
+        rawLine = rawLine.substring(start, end);
+        for (var j = 0; j < entities.length; j++) {
+          for (var index = 0; index < rawLine.length; index++) {
+            var c = rawLine[index];
+            if (c === entities[j]) {
+              context.report({
+                loc: {line: i, column: start + index},
+                message: 'HTML entities must be escaped.',
+                node: node
+              });
+            }
+          }
+        }
+      }
+    }
+
+    return {
+      Literal: function(node) {
+        if (node.type === 'Literal' && node.parent.type === 'JSXElement') {
+          if (isInvalidEntity(node)) {
+            context.report(node, 'HTML entities must be escaped.');
+          }
+        }
+      }
+    };
+  }
+};


Test patch:
diff --git a/tests/lib/rules/jsx-indent.js b/tests/lib/rules/jsx-indent.js
index d2f7892f..3eb7ba9d 100644
--- a/tests/lib/rules/jsx-indent.js
+++ b/tests/lib/rules/jsx-indent.js
@@ -162,5 +162,56 @@ ruleTester.run('jsx-indent', rule, {
     options: [2],
     parserOptions: parserOptions,
     errors: [{message: 'Expected indentation of 4 space characters but found 0.'}]
+  }, {
+    code: [
+      '<App>',
+      '   {test}',
+      '</App>'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    errors: [
+      {message: 'Expected indentation of 4 space characters but found 3.'}
+    ]
+  }, {
+    code: [
+      '<App>',
+      '    {options.map((option, index) => (',
+      '        <option key={index} value={option.key}>',
+      '           {option.name}',
+      '        </option>',
+      '    ))}',
+      '</App>'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    errors: [
+      {message: 'Expected indentation of 12 space characters but found 11.'}
+    ]
+  }, {
+    code: [
+      '<App>',
+      '{test}',
+      '</App>'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    options: ['tab'],
+    errors: [
+      {message: 'Expected indentation of 1 tab character but found 0.'}
+    ]
+  }, {
+    code: [
+      '<App>',
+      '\t{options.map((option, index) => (',
+      '\t\t<option key={index} value={option.key}>',
+      '\t\t{option.name}',
+      '\t\t</option>',
+      '\t))}',
+      '</App>'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    options: ['tab'],
+    errors: [
+      {message: 'Expected indentation of 3 tab characters but found 2.'}
+    ]
   }]
 });
+
diff --git a/tests/lib/rules/no-children-prop.js b/tests/lib/rules/no-children-prop.js
new file mode 100644
index 00000000..d0073dc0
--- /dev/null
+++ b/tests/lib/rules/no-children-prop.js
@@ -0,0 +1,237 @@
+/**
+ * @fileoverview Tests for no-children-prop
+ * @author Benjamin Stepp
+ */
+
+'use strict';
+
+// -----------------------------------------------------------------------------
+// Requirements
+// -----------------------------------------------------------------------------
+
+var rule = require('../../../lib/rules/no-children-prop');
+var RuleTester = require('eslint').RuleTester;
+
+var parserOptions = {
+  ecmaVersion: 6,
+  ecmaFeatures: {
+    jsx: true
+  }
+};
+
+var JSX_ERROR = 'Do not pass children as props. Instead, nest children between \
+the opening and closing tags.';
+var CREATE_ELEMENT_ERROR = 'Do not pass children as props. Instead, pass them \
+as additional arguments to React.createElement.';
+
+// -----------------------------------------------------------------------------
+// Tests
+// -----------------------------------------------------------------------------
+
+var ruleTester = new RuleTester();
+ruleTester.run('no-children-prop', rule, {
+  valid: [
+    {
+      code: '<div />;',
+      parserOptions: parserOptions
+    },
+    {
+      code: '<div></div>;',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", {});',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", undefined);',
+      parserOptions: parserOptions
+    },
+    {
+      code: '<div className="class-name"></div>;',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", {className: "class-name"});',
+      parserOptions: parserOptions
+    },
+    {
+      code: '<div>Children</div>;',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", "Children");',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", {}, "Children");',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", undefined, "Children");',
+      parserOptions: parserOptions
+    },
+    {
+      code: '<div className="class-name">Children</div>;',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", {className: "class-name"}, "Children");',
+      parserOptions: parserOptions
+    },
+    {
+      code: '<div><div /></div>;',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", React.createElement("div"));',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", {}, React.createElement("div"));',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", undefined, React.createElement("div"));',
+      parserOptions: parserOptions
+    },
+    {
+      code: '<div><div /><div /></div>;',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", React.createElement("div"), React.createElement("div"));',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", {}, React.createElement("div"), React.createElement("div"));',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", undefined, React.createElement("div"), React.createElement("div"));',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", [React.createElement("div"), React.createElement("div")]);',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", {}, [React.createElement("div"), React.createElement("div")]);',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", undefined, [React.createElement("div"), React.createElement("div")]);',
+      parserOptions: parserOptions
+    },
+    {
+      code: '<MyComponent />',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement(MyComponent);',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement(MyComponent, {});',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement(MyComponent, undefined);',
+      parserOptions: parserOptions
+    },
+    {
+      code: '<MyComponent>Children</MyComponent>;',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement(MyComponent, "Children");',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement(MyComponent, {}, "Children");',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement(MyComponent, undefined, "Children");',
+      parserOptions: parserOptions
+    },
+    {
+      code: '<MyComponent className="class-name"></MyComponent>;',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement(MyComponent, {className: "class-name"});',
+      parserOptions: parserOptions
+    },
+    {
+      code: '<MyComponent className="class-name">Children</MyComponent>;',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement(MyComponent, {className: "class-name"}, "Children");',
+      parserOptions: parserOptions
+    }
+  ],
+  invalid: [
+    {
+      code: '<div children="Children" />;',
+      errors: [{message: JSX_ERROR}],
+      parserOptions: parserOptions
+    },
+    {
+      code: '<div children={<div />} />;',
+      errors: [{message: JSX_ERROR}],
+      parserOptions: parserOptions
+    },
+    {
+      code: '<div children={[<div />, <div />]} />;',
+      errors: [{message: JSX_ERROR}],
+      parserOptions: parserOptions
+    },
+    {
+      code: '<div children="Children">Children</div>;',
+      errors: [{message: JSX_ERROR}],
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", {children: "Children"});',
+      errors: [{message: CREATE_ELEMENT_ERROR}],
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", {children: "Children"}, "Children");',
+      errors: [{message: CREATE_ELEMENT_ERROR}],
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", {children: React.createElement("div")});',
+      errors: [{message: CREATE_ELEMENT_ERROR}],
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", {children: [React.createElement("div"), React.createElement("div")]});',
+      errors: [{message: CREATE_ELEMENT_ERROR}],
+      parserOptions: parserOptions
+    },
+    {
+      code: '<MyComponent children="Children" />',
+      errors: [{message: JSX_ERROR}],
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement(MyComponent, {children: "Children"});',
+      errors: [{message: CREATE_ELEMENT_ERROR}],
+      parserOptions: parserOptions
+    },
+    {
+      code: '<MyComponent className="class-name" children="Children" />;',
+      errors: [{message: JSX_ERROR}],
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement(MyComponent, {children: "Children", className: "class-name"});',
+      errors: [{message: CREATE_ELEMENT_ERROR}],
+      parserOptions: parserOptions
+    }
+  ]
+});
diff --git a/tests/lib/rules/no-unescaped-entities.js b/tests/lib/rules/no-unescaped-entities.js
new file mode 100644
index 00000000..098dd4f7
--- /dev/null
+++ b/tests/lib/rules/no-unescaped-entities.js
@@ -0,0 +1,138 @@
+/**
+ * @fileoverview Tests for no-unescaped-entities
+ * @author Patrick Hayes
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require('../../../lib/rules/no-unescaped-entities');
+var RuleTester = require('eslint').RuleTester;
+var parserOptions = {
+  ecmaFeatures: {
+    jsx: true
+  }
+};
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+var ruleTester = new RuleTester();
+ruleTester.run('no-unescaped-entities', rule, {
+
+  valid: [
+    {
+      code: [
+        'var Hello = React.createClass({',
+        '  render: function() {',
+        '    return (',
+        '      <div/>',
+        '    );',
+        '  }',
+        '});'
+      ].join('\n'),
+      parserOptions: parserOptions
+    }, {
+      code: [
+        'var Hello = React.createClass({',
+        '  render: function() {',
+        '    return <div>Here is some text!</div>;',
+        '  }',
+        '});'
+      ].join('\n'),
+      parserOptions: parserOptions
+    }, {
+      code: [
+        'var Hello = React.createClass({',
+        '  render: function() {',
+        '    return <div>I&rsquo;ve escaped some entities: &gt; &lt; &amp;</div>;',
+        '  }',
+        '});'
+      ].join('\n'),
+      parserOptions: parserOptions
+    }, {
+      code: [
+        'var Hello = React.createClass({',
+        '  render: function() {',
+        '    return <div>first line is ok',
+        '    so is second',
+        '    and here are some escaped entities: &gt; &lt; &amp;</div>;',
+        '  }',
+        '});'
+      ].join('\n'),
+      parserOptions: parserOptions
+    }, {
+      code: [
+        'var Hello = React.createClass({',
+        '  render: function() {',
+        '    return <div>{">" + "<" + "&" + \'"\'}</div>;',
+        '  },',
+        '});'
+      ].join('\n'),
+      parserOptions: parserOptions
+    }
+  ],
+
+  invalid: [
+    {
+      code: [
+        'var Hello = React.createClass({',
+        '  render: function() {',
+        '    return <div>></div>;',
+        '  }',
+        '});'
+      ].join('\n'),
+      parserOptions: parserOptions,
+      errors: [{message: 'HTML entities must be escaped.'}]
+    }, {
+      code: [
+        'var Hello = React.createClass({',
+        '  render: function() {',
+        '    return <div>first line is ok',
+        '    so is second',
+        '    and here are some bad entities: ></div>',
+        '  }',
+        '});'
+      ].join('\n'),
+      parserOptions: parserOptions,
+      errors: [{message: 'HTML entities must be escaped.'}]
+    }, {
+      code: [
+        'var Hello = React.createClass({',
+        '  render: function() {',
+        '    return <div>\'</div>;',
+        '  }',
+        '});'
+      ].join('\n'),
+      parserOptions: parserOptions,
+      errors: [{message: 'HTML entities must be escaped.'}]
+    }, {
+      code: [
+        'var Hello = React.createClass({',
+        '  render: function() {',
+        '    return <div>Multiple errors: \'>></div>;',
+        '  }',
+        '});'
+      ].join('\n'),
+      parserOptions: parserOptions,
+      errors: [
+        {message: 'HTML entities must be escaped.'},
+        {message: 'HTML entities must be escaped.'},
+        {message: 'HTML entities must be escaped.'}
+      ]
+    }, {
+      code: [
+        'var Hello = React.createClass({',
+        '  render: function() {',
+        '    return <div>{"Unbalanced braces"}}</div>;',
+        '  }',
+        '});'
+      ].join('\n'),
+      parserOptions: parserOptions,
+      errors: [{message: 'HTML entities must be escaped.'}]
+    }
+  ]
+});