about summary refs log tree commit diff
path: root/eslint
diff options
context:
space:
mode:
authordan <dan.abramov@gmail.com>2024-04-04 21:34:55 +0100
committerGitHub <noreply@github.com>2024-04-04 21:34:55 +0100
commit3915bb43169ae501d81571c5e1efa12cf0e24dbb (patch)
treebe2f7bed7c842be71922f2793b4b4a20cd6fbc24 /eslint
parentc190fd58ec82b36ea8124902cad34acc4a5b5ac0 (diff)
downloadvoidsky-3915bb43169ae501d81571c5e1efa12cf0e24dbb.tar.zst
Enforce Text suffix for Text-rendering components (#3407)
* Rm unused

* Add Text suffix to Title/Description

* Add Text suffix to text components

* Add Text suffix to props

* Validate Text components returns
Diffstat (limited to 'eslint')
-rw-r--r--eslint/__tests__/avoid-unwrapped-text.test.js65
-rw-r--r--eslint/avoid-unwrapped-text.js38
2 files changed, 102 insertions, 1 deletions
diff --git a/eslint/__tests__/avoid-unwrapped-text.test.js b/eslint/__tests__/avoid-unwrapped-text.test.js
index 0fbc01123..7c667b4a8 100644
--- a/eslint/__tests__/avoid-unwrapped-text.test.js
+++ b/eslint/__tests__/avoid-unwrapped-text.test.js
@@ -246,6 +246,41 @@ describe('avoid-unwrapped-text', () => {
 </Foo>
               `,
       },
+
+      {
+        code: `
+function Stuff() {
+  return <Text>foo</Text>
+}
+        `,
+      },
+
+      {
+        code: `
+function Stuff({ foo }) {
+  return <View>{foo}</View>
+}
+        `,
+      },
+
+      {
+        code: `
+function MyText() {
+  return <Text>foo</Text>
+}
+        `,
+      },
+
+      {
+        code: `
+function MyText({ foo }) {
+  if (foo) {
+    return <Text>foo</Text>
+  }
+  return <Text>foo</Text>
+}
+        `,
+      },
     ],
 
     invalid: [
@@ -390,6 +425,36 @@ describe('avoid-unwrapped-text', () => {
         `,
         errors: 1,
       },
+
+      {
+        code: `
+function MyText() {
+  return <Foo />
+}
+        `,
+        errors: 1,
+      },
+
+      {
+        code: `
+function MyText({ foo }) {
+  return <Foo>{foo}</Foo>
+}
+        `,
+        errors: 1,
+      },
+
+      {
+        code: `
+function MyText({ foo }) {
+  if (foo) {
+    return <Foo>{foo}</Foo>
+  }
+  return <Text>foo</Text>
+}
+        `,
+        errors: 1,
+      },
     ],
   }
 
diff --git a/eslint/avoid-unwrapped-text.js b/eslint/avoid-unwrapped-text.js
index c9e72386e..79d099f00 100644
--- a/eslint/avoid-unwrapped-text.js
+++ b/eslint/avoid-unwrapped-text.js
@@ -35,6 +35,11 @@ exports.create = function create(context) {
   const impliedTextComponents = options.impliedTextComponents ?? []
   const textProps = [...impliedTextProps]
   const textComponents = ['Text', ...impliedTextComponents]
+
+  function isTextComponent(tagName) {
+    return textComponents.includes(tagName) || tagName.endsWith('Text')
+  }
+
   return {
     JSXText(node) {
       if (typeof node.value !== 'string' || hasOnlyLineBreak(node.value)) {
@@ -44,7 +49,7 @@ exports.create = function create(context) {
       while (parent) {
         if (parent.type === 'JSXElement') {
           const tagName = getTagName(parent)
-          if (textComponents.includes(tagName) || tagName.endsWith('Text')) {
+          if (isTextComponent(tagName)) {
             // We're good.
             return
           }
@@ -107,5 +112,36 @@ exports.create = function create(context) {
         continue
       }
     },
+    ReturnStatement(node) {
+      let fnScope = context.getScope()
+      while (fnScope && fnScope.type !== 'function') {
+        fnScope = fnScope.upper
+      }
+      if (!fnScope) {
+        return
+      }
+      const fn = fnScope.block
+      if (!fn.id || fn.id.type !== 'Identifier' || !fn.id.name) {
+        return
+      }
+      if (!/^[A-Z]\w*Text$/.test(fn.id.name)) {
+        return
+      }
+      if (!node.argument || node.argument.type !== 'JSXElement') {
+        return
+      }
+      const openingEl = node.argument.openingElement
+      if (openingEl.name.type !== 'JSXIdentifier') {
+        return
+      }
+      const returnedComponentName = openingEl.name.name
+      if (!isTextComponent(returnedComponentName)) {
+        context.report({
+          node,
+          message:
+            'Components ending with *Text must return <Text> or <SomeText>.',
+        })
+      }
+    },
   }
 }