Repository: yannickcr/eslint-plugin-react
Pull request #176 (2015-08-01 23:31:16)

New rule: 'no-literals'
This is new style rule that enforces wrapping literal strings in a `JSXExpressionContainer` element.

`<div>{'text'}</div>` and not allowing `<div>text</div>`.

Besides being sure of the actual rendered text, this also helps removed problems with some highlighters and JSX.

```javascript
<div>{"this can't possibly be a problem"}</div>
<div>this can't possibly be a problem</div>
```

As you see the `'` causes problems with the rest of the code, despite being valid.

I'm unsure if `no-literals` is an appropriate name, but since that seems to be the name of the node type I went with it.


Patch:
diff --git a/README.md b/README.md
index 9c0f65ac..ef8b91f2 100644
--- a/README.md
+++ b/README.md
@@ -75,6 +75,7 @@ Finally, enable all of the rules that you would like to use.
 * [jsx-curly-spacing](docs/rules/jsx-curly-spacing.md): Enforce or disallow spaces inside of curly braces in JSX attributes
 * [jsx-max-props-per-line](docs/rules/jsx-max-props-per-line.md): Limit maximum of props on a single line in JSX
 * [jsx-no-duplicate-props](docs/rules/jsx-no-duplicate-props.md): Prevent duplicate props in JSX
+* [jsx-no-literals](docs/rules/jsx-no-literals.md): Prevent usage of unwrapped JSX strings
 * [jsx-no-undef](docs/rules/jsx-no-undef.md): Disallow undeclared variables in JSX
 * [jsx-quotes](docs/rules/jsx-quotes.md): Enforce quote style for JSX attributes
 * [jsx-sort-prop-types](docs/rules/jsx-sort-prop-types.md): Enforce propTypes declarations alphabetical sorting
diff --git a/docs/rules/jsx-no-literals.md b/docs/rules/jsx-no-literals.md
new file mode 100644
index 00000000..a0528c7c
--- /dev/null
+++ b/docs/rules/jsx-no-literals.md
@@ -0,0 +1,23 @@
+# Prevent usage of unwrapped JSX strings (jsx-no-literals)
+
+In JSX when using a literal string you can wrap it in a JXS container `{'TEXT'}`.
+This rules requies that you wrap all literal strings.
+Prevents any odd artifacts of highlighters if you unwrapped string contains a enclsoing character like `'` in contractions and enforces consistency.
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```javascript
+var Hello = <div>test</div>;
+```
+
+The following patterns are not considered:
+
+```javascript
+var Hello = <div>{'test'}</div>;
+```
+
+## When Not To Use It
+
+If you do not want to enforce any style JSX literals, then you can disable this rule.
diff --git a/index.js b/index.js
index 379a2f47..433ce8c7 100644
--- a/index.js
+++ b/index.js
@@ -23,7 +23,8 @@ module.exports = {
     'sort-comp': require('./lib/rules/sort-comp'),
     'require-extension': require('./lib/rules/require-extension'),
     'jsx-no-duplicate-props': require('./lib/rules/jsx-no-duplicate-props'),
-    'jsx-max-props-per-line': require('./lib/rules/jsx-max-props-per-line')
+    'jsx-max-props-per-line': require('./lib/rules/jsx-max-props-per-line'),
+    'jsx-no-literals': require('./lib/rules/jsx-no-literals')
   },
   rulesConfig: {
     'jsx-uses-react': 0,
@@ -47,6 +48,7 @@ module.exports = {
     'sort-comp': 0,
     'require-extension': 0,
     'jsx-no-duplicate-props': 0,
-    'jsx-max-props-per-line': 0
+    'jsx-max-props-per-line': 0,
+    'jsx-no-literals': 0
   }
 };
diff --git a/lib/rules/jsx-no-literals.js b/lib/rules/jsx-no-literals.js
new file mode 100644
index 00000000..99f31a08
--- /dev/null
+++ b/lib/rules/jsx-no-literals.js
@@ -0,0 +1,41 @@
+/*
+* @fileoverview Prevent using string literals in React component definition
+* @author Caleb Morris
+*/
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+  function reportLiteralNode(node) {
+    context.report(node, 'Missing JSX expression container around literal string');
+  }
+
+  // --------------------------------------------------------------------------
+  // Public
+  // --------------------------------------------------------------------------
+
+  return {
+
+    Literal: function(node) {
+      if (
+       !/^[\s]+$/.test(node.value) &&
+        node.parent &&
+        node.parent.type !== 'JSXExpressionContainer'
+      ) {
+        reportLiteralNode(node);
+      }
+    }
+
+  };
+
+};
+
+module.exports.schema = [{
+  type: 'object',
+  properties: {},
+  additionalProperties: false
+}];


Test patch:
diff --git a/tests/lib/rules/jsx-no-literals.js b/tests/lib/rules/jsx-no-literals.js
new file mode 100644
index 00000000..8bb05651
--- /dev/null
+++ b/tests/lib/rules/jsx-no-literals.js
@@ -0,0 +1,199 @@
+/**
+ * @fileoverview Prevent using unwrapped literals in a React component definition
+ * @author Caleb morris
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var eslint = require('eslint').linter;
+var ESLintTester = require('eslint').ESLintTester;
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+var eslintTester = new ESLintTester(eslint);
+eslintTester.addRuleTest('lib/rules/jsx-no-literals', {
+
+  valid: [
+    {
+      code: [
+        'class Comp1 extends Component {',
+        '  render() {',
+        '    return (',
+        '      <div>',
+        '        {\'asdjfl\'}',
+        '      </div>',
+        '    );',
+        '  }',
+        '}'
+      ].join('\n'),
+      args: [1],
+      parser: 'babel-eslint'
+    }, {
+      code: [
+        'class Comp1 extends Component {',
+        '  render() {',
+        '    return (<div>{\'test\'}</div>);',
+        '  }',
+        '}'
+      ].join('\n'),
+      args: [1],
+      parser: 'babel-eslint'
+    }, {
+      code: [
+        'class Comp1 extends Component {',
+        '  render() {',
+        '    const bar = (<div>{\'hello\'}</div>);',
+        '    return bar;',
+        '  }',
+        '}'
+      ].join('\n'),
+      args: [1],
+      parser: 'babel-eslint'
+    }, {
+      code: [
+        'var Hello = React.createClass({',
+        '  foo: (<div>{\'hello\'}</div>),',
+        '  render() {',
+        '    return this.foo;',
+        '  },',
+        '});'
+      ].join('\n'),
+      args: [1],
+      parser: 'babel-eslint'
+    }, {
+      code: [
+        'class Comp1 extends Component {',
+        '  render() {',
+        '    return (',
+        '      <div>',
+        '        {\'asdjfl\'}',
+        '        {\'test\'}',
+        '        {\'foo\'}',
+        '      </div>',
+        '    );',
+        '  }',
+        '}'
+      ].join('\n'),
+      args: [1],
+      parser: 'babel-eslint'
+    }, {
+      code: [
+        'class Comp1 extends Component {',
+        '  render() {',
+        '    return (',
+        '      <div>',
+        '      </div>',
+        '    );',
+        '  }',
+        '}'
+      ].join('\n'),
+      args: [1],
+      parser: 'babel-eslint'
+    }
+  ],
+
+  invalid: [
+    {
+      code: [
+        'class Comp1 extends Component {',
+        '  render() {',
+        '    return (<div>test</div>);',
+        '  }',
+        '}'
+      ].join('\n'),
+      args: [1],
+      parser: 'babel-eslint',
+      errors: [{message: 'Missing JSX expression container around literal string'}]
+    }, {
+      code: [
+        'class Comp1 extends Component {',
+        '  render() {',
+        '    const foo = (<div>test</div>);',
+        '    return foo;',
+        '  }',
+        '}'
+      ].join('\n'),
+      args: [1],
+      parser: 'babel-eslint',
+      errors: [{message: 'Missing JSX expression container around literal string'}]
+    }, {
+      code: [
+        'class Comp1 extends Component {',
+        '  render() {',
+        '    const varObjectTest = { testKey : (<div>test</div>) };',
+        '    return varObjectTest.testKey;',
+        '  }',
+        '}'
+      ].join('\n'),
+      args: [1],
+      parser: 'babel-eslint',
+      errors: [{message: 'Missing JSX expression container around literal string'}]
+    }, {
+      code: [
+        'var Hello = React.createClass({',
+        '  foo: (<div>hello</div>),',
+        '  render() {',
+        '    return this.foo;',
+        '  },',
+        '});'
+      ].join('\n'),
+      args: [1],
+      parser: 'babel-eslint',
+      errors: [{message: 'Missing JSX expression container around literal string'}]
+    }, {
+      code: [
+        'class Comp1 extends Component {',
+        '  render() {',
+        '    return (',
+        '      <div>',
+        '        asdjfl',
+        '      </div>',
+        '    );',
+        '  }',
+        '}'
+      ].join('\n'),
+      args: [1],
+      parser: 'babel-eslint',
+      errors: [{message: 'Missing JSX expression container around literal string'}]
+    }, {
+      code: [
+        'class Comp1 extends Component {',
+        '  render() {',
+        '    return (',
+        '      <div>',
+        '        asdjfl',
+        '        test',
+        '        foo',
+        '      </div>',
+        '    );',
+        '  }',
+        '}'
+      ].join('\n'),
+      args: [1],
+      parser: 'babel-eslint',
+      errors: [{message: 'Missing JSX expression container around literal string'}]
+    }, {
+      code: [
+        'class Comp1 extends Component {',
+        '  render() {',
+        '    return (',
+        '      <div>',
+        '        {\'asdjfl\'}',
+        '        test',
+        '        {\'foo\'}',
+        '      </div>',
+        '    );',
+        '  }',
+        '}'
+      ].join('\n'),
+      args: [1],
+      parser: 'babel-eslint',
+      errors: [{message: 'Missing JSX expression container around literal string'}]
+    }
+  ]
+});