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
This commit is contained in:
dan 2024-04-04 21:34:55 +01:00 committed by GitHub
parent c190fd58ec
commit 3915bb4316
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 453 additions and 366 deletions

View file

@ -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,
},
],
}

View file

@ -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>.',
})
}
},
}
}