From 59f41ca6634b8d0ce822637b0da16fd373c0e2df Mon Sep 17 00:00:00 2001
From: AleksandrZhukov <aleksandr.a.zhukov@gmail.com>
Date: Tue, 2 Oct 2018 12:23:40 +0300
Subject: [PATCH] Added no-raw-text rule

- added new rule to prevent error of raw text, eg
  https://github.com/facebook/react-native/issues/15870
  https://github.com/react-community/react-native-maps/issues/1791
- added .idea folder to gitignore
---
 .gitignore                     |   2 +
 README.md                      |   2 +
 docs/rules/no-raw-text.md      |  26 ++++++++
 index.js                       |   2 +
 lib/rules/no-raw-text.js       |  63 +++++++++++++++++++
 tests/lib/rules/no-raw-text.js | 108 +++++++++++++++++++++++++++++++++
 6 files changed, 203 insertions(+)
 create mode 100644 docs/rules/no-raw-text.md
 create mode 100644 lib/rules/no-raw-text.js
 create mode 100644 tests/lib/rules/no-raw-text.js

diff --git a/.gitignore b/.gitignore
index 123ae94..d532efc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,5 @@ build/Release
 # Dependency directory
 # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
 node_modules
+
+.idea
diff --git a/README.md b/README.md
index d674389..c64fc48 100644
--- a/README.md
+++ b/README.md
@@ -71,6 +71,7 @@ Finally, enable all of the rules that you would like to use.
     "react-native/split-platform-components": 2,
     "react-native/no-inline-styles": 2,
     "react-native/no-color-literals": 2,
+    "react-native/no-raw-text": 2,
   }
 }
 ```
@@ -81,6 +82,7 @@ Finally, enable all of the rules that you would like to use.
 * [split-platform-components](docs/rules/split-platform-components.md): Enforce using platform specific filenames when necessary
 * [no-inline-styles](docs/rules/no-inline-styles.md): Detect JSX components with inline styles that contain literal values
 * [no-color-literals](docs/rules/no-color-literals.md): Detect `StyleSheet` rules and inline styles containing color literals instead of variables
+* [no-raw-text](docs/rules/no-raw-text.md): Detect raw text outside of `Text`  component
 
 [npm-url]: https://npmjs.org/package/eslint-plugin-react-native
 [npm-image]: http://img.shields.io/npm/v/eslint-plugin-react-native.svg?style=flat-square
diff --git a/docs/rules/no-raw-text.md b/docs/rules/no-raw-text.md
new file mode 100644
index 0000000..c5a038e
--- /dev/null
+++ b/docs/rules/no-raw-text.md
@@ -0,0 +1,26 @@
+# Detect raw text outside of Text component
+All strings in React Native should be wrapped with a Text component.
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```js
+<View>some text</View>
+```
+
+```js
+const text = 'some text';
+<View>{`${text}`}</View>
+```
+
+The following patterns are not considered warnings:
+
+```js
+<View><Text>some text</Text></View>
+```
+
+```js
+const text = 'some text';
+<View><Text>{`${text}`}</Text></View>
+```
diff --git a/index.js b/index.js
index ddbc6e1..abc99bc 100644
--- a/index.js
+++ b/index.js
@@ -7,6 +7,7 @@ const allRules = {
   'no-inline-styles': require('./lib/rules/no-inline-styles'),
   'no-color-literals': require('./lib/rules/no-color-literals'),
   'split-platform-components': require('./lib/rules/split-platform-components'),
+  'no-raw-text': require('./lib/rules/no-raw-text'),
 };
 
 function configureAsError(rules) {
@@ -30,6 +31,7 @@ module.exports = {
     'no-inline-styles': 0,
     'no-color-literals': 0,
     'split-platform-components': 0,
+    'no-raw-text': 0,
   },
   environments: {
     'react-native': {
diff --git a/lib/rules/no-raw-text.js b/lib/rules/no-raw-text.js
new file mode 100644
index 0000000..4c1340c
--- /dev/null
+++ b/lib/rules/no-raw-text.js
@@ -0,0 +1,63 @@
+/**
+ * @fileoverview Detects raw text outside of Text component
+ * @author Alex Zhukov
+ */
+
+'use strict';
+
+module.exports = (context) => {
+  const elementName = node => (
+    node.openingElement &&
+    node.openingElement.name &&
+    node.openingElement.name.type === 'JSXIdentifier' &&
+    node.openingElement.name.name
+  );
+
+  const report = (node) => {
+    const errorValue = node.type === 'TemplateLiteral'
+      ? `TemplateLiteral: ${node.expressions[0].name}`
+      : node.value;
+    context.report({
+      node,
+      message: `Raw text (${errorValue.trim()}) cannot be used outside of a <Text> tag`,
+    });
+  };
+
+  const getValidation = node => elementName(node.parent) !== 'Text';
+
+  return {
+    Literal(node) {
+      const parentType = node.parent.type;
+      const onlyFor = ['JSXExpressionContainer', 'JSXElement'];
+      if (/^[\s]+$/.test(node.value) ||
+        typeof node.value !== 'string' ||
+        !onlyFor.includes(parentType) ||
+        (node.parent.parent && node.parent.parent.type === 'JSXAttribute')
+      ) return;
+
+      const isStringLiteral = parentType === 'JSXExpressionContainer';
+      if (getValidation(isStringLiteral ? node.parent : node)) {
+        report(node);
+      }
+    },
+
+    JSXText(node) {
+      if (getValidation(node)) {
+        report(node);
+      }
+    },
+
+    TemplateLiteral(node) {
+      if (
+        node.parent.type !== 'JSXExpressionContainer' ||
+        (node.parent.parent && node.parent.parent.type === 'JSXAttribute')
+      ) return;
+
+      if (getValidation(node.parent)) {
+        report(node);
+      }
+    },
+  };
+};
+
+module.exports.schema = [];
diff --git a/tests/lib/rules/no-raw-text.js b/tests/lib/rules/no-raw-text.js
new file mode 100644
index 0000000..dc25f8b
--- /dev/null
+++ b/tests/lib/rules/no-raw-text.js
@@ -0,0 +1,108 @@
+/**
+ * @fileoverview Detects raw text outside of Text component
+ * @author Alex Zhukov
+ */
+
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-raw-text');
+const RuleTester = require('eslint').RuleTester;
+
+require('babel-eslint');
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester();
+
+const tests = {
+  valid: [
+    {
+      code: `
+        export default class MyComponent extends Component {
+          render() {
+            return (<View><Text>some text</Text></View>);
+          }
+        }
+      `,
+    },
+    {
+      code: `
+        export default class MyComponent extends Component {
+          render() {
+            const text = 'some text';
+            return (<View><Text>{\`\${text}\`}</Text></View>);
+          }
+        }
+      `,
+    },
+    {
+      code: `
+        export default class MyComponent extends Component {
+          render() {
+            return (<View><Text>{'some text'}</Text></View>);
+          }
+        }
+      `,
+    },
+  ],
+  invalid: [
+    {
+      code: `
+        export default class MyComponent extends Component {
+          render() {
+            return (<View>some text</View>);
+          }
+        }
+      `,
+      errors: [{
+        message: 'Raw text (some text) cannot be used outside of a <Text> tag',
+      }],
+    },
+    {
+      code: `
+        export default class MyComponent extends Component {
+          render() {
+            const text = 'some text';
+            return (<View>{\`\${text}\`}</View>);
+          }
+        }
+      `,
+      errors: [{
+        message: 'Raw text (TemplateLiteral: text) cannot be used outside of a <Text> tag',
+      }],
+    },
+    {
+      code: `
+        export default class MyComponent extends Component {
+          render() {
+            return (<View>{'some text'}</View>);
+          }
+        }
+      `,
+      errors: [{
+        message: 'Raw text (some text) cannot be used outside of a <Text> tag',
+      }],
+    },
+  ],
+};
+
+const config = {
+  parser: 'babel-eslint',
+  parserOptions: {
+    ecmaFeatures: {
+      classes: true,
+      jsx: true,
+    },
+  },
+};
+
+tests.valid.forEach(t => Object.assign(t, config));
+tests.invalid.forEach(t => Object.assign(t, config));
+
+ruleTester.run('no-raw-text', rule, tests);