diff --git a/.changelog/1984.internal.md b/.changelog/1984.internal.md
new file mode 100644
index 0000000000..f711477174
--- /dev/null
+++ b/.changelog/1984.internal.md
@@ -0,0 +1 @@
+Lint rule to detect broken Google Translate in CommissionBounds
diff --git a/.eslintrc.js b/.eslintrc.js
index 8c6f5ad93b..2c6a866f36 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -68,6 +68,21 @@ const noGoogleTranslateCrashingSyntax = [
'Plain text nodes before or after a condition could break React if used with Google Translate. Identifier could possibly return a string, so wrap it into an element.',
},
+ // Ban components returning text or variables inside fragments `return <>{t('text')}>` (just in case vars return a string and parent components are conditionally inserted)
+ // TODO: doesn't catch `return t('text')`
+ {
+ selector:
+ ':matches(ArrowFunctionExpression, ReturnStatement) > JSXFragment > JSXExpressionContainer > :not(' +
+ ' JSXEmptyExpression, ' + // Allow `<>{/* Comment */}>`
+ ' ConditionalExpression[consequent.type="JSXElement"][alternate.type="JSXElement"], ' + // Allow `<>{condition ? : }>`
+ ' LogicalExpression[right.type="JSXElement"], ' + // Allow `<>{condition && }>`
+ ' Identifier[name="children"], ' + // Allow `<>{children}>`
+ ' MemberExpression[property.name="children"]' + // Allow `<>{someProps.children}>`
+ ')',
+ message:
+ 'Text nodes inside React fragments could break React if used with Google Translate. Identifier could possibly return a string, and parent components could be conditionally inserted, so wrap it into an element.',
+ },
+
// TODO: Nesting is not supported. Detect it as error for now
{
selector: 'JSXElement > JSXExpressionContainer > ConditionalExpression > ConditionalExpression',
diff --git a/src/app/components/DateFormatter/index.tsx b/src/app/components/DateFormatter/index.tsx
index 53aa2fd18d..303c192765 100644
--- a/src/app/components/DateFormatter/index.tsx
+++ b/src/app/components/DateFormatter/index.tsx
@@ -11,5 +11,5 @@ interface Props {
}
export function DateFormatter(props: Props) {
- return <>{intlDateTimeFormat(props.date)}>
+ return {intlDateTimeFormat(props.date)}
}
diff --git a/src/app/components/ErrorFormatter/index.tsx b/src/app/components/ErrorFormatter/index.tsx
index 96c9719b92..fc5a73d838 100644
--- a/src/app/components/ErrorFormatter/index.tsx
+++ b/src/app/components/ErrorFormatter/index.tsx
@@ -105,5 +105,5 @@ export function ErrorFormatter(props: Props) {
}
const error = errorMap[props.code]
- return <>{error}>
+ return {error}
}
diff --git a/src/app/components/ImportAccountsStepFormatter/index.tsx b/src/app/components/ImportAccountsStepFormatter/index.tsx
index a1e4ca1fd6..69cd4c13fb 100644
--- a/src/app/components/ImportAccountsStepFormatter/index.tsx
+++ b/src/app/components/ImportAccountsStepFormatter/index.tsx
@@ -19,5 +19,5 @@ export const ImportAccountsStepFormatter = memo((props: Props) => {
}
const message = stepMap[step]
- return <>{message}>
+ return {message}
})
diff --git a/src/app/components/TimeToEpoch/index.tsx b/src/app/components/TimeToEpoch/index.tsx
index b4820c15fb..d3cbc46b29 100644
--- a/src/app/components/TimeToEpoch/index.tsx
+++ b/src/app/components/TimeToEpoch/index.tsx
@@ -20,5 +20,5 @@ export function TimeToEpoch(props: Props) {
? relativeFormat.format(Math.round(remainingHours / 24), 'day')
: relativeFormat.format(remainingHours, 'hour')
- return <>{formattedRemainingTime}>
+ return {formattedRemainingTime}
}
diff --git a/src/app/components/Transaction/__tests__/index.test.tsx b/src/app/components/Transaction/__tests__/index.test.tsx
index d2e1de0d10..a7b22e621e 100644
--- a/src/app/components/Transaction/__tests__/index.test.tsx
+++ b/src/app/components/Transaction/__tests__/index.test.tsx
@@ -19,7 +19,7 @@ jest.mock('copy-to-clipboard')
type TransType = typeof Trans
jest.mock('react-i18next', () => ({
- Trans: (({ i18nKey }) => <>{i18nKey}>) as TransType,
+ Trans: (({ i18nKey }) => i18nKey) as TransType,
useTranslation: () => {
return {
t: (str: string) => str,
diff --git a/src/app/pages/ParaTimesPage/TransactionError/__tests__/__snapshots__/index.test.tsx.snap b/src/app/pages/ParaTimesPage/TransactionError/__tests__/__snapshots__/index.test.tsx.snap
index 038a37f481..6c757ed0ba 100644
--- a/src/app/pages/ParaTimesPage/TransactionError/__tests__/__snapshots__/index.test.tsx.snap
+++ b/src/app/pages/ParaTimesPage/TransactionError/__tests__/__snapshots__/index.test.tsx.snap
@@ -534,7 +534,9 @@ exports[` should render component 1`] = `
class="c11"
style="vertical-align: middle;"
>
- Unknown ParaTime error: error message
+
+ Unknown ParaTime error: error message
+
diff --git a/src/app/pages/StakingPage/Features/DelegationList/__tests__/__snapshots__/DebondingDelegationList.test.tsx.snap b/src/app/pages/StakingPage/Features/DelegationList/__tests__/__snapshots__/DebondingDelegationList.test.tsx.snap
index 896729f3ba..47921ad9d4 100644
--- a/src/app/pages/StakingPage/Features/DelegationList/__tests__/__snapshots__/DebondingDelegationList.test.tsx.snap
+++ b/src/app/pages/StakingPage/Features/DelegationList/__tests__/__snapshots__/DebondingDelegationList.test.tsx.snap
@@ -595,7 +595,9 @@ exports[` should match snapshot 1`] = `
id="cell-debondingTimeEnd-test-validator+100"
role="gridcell"
>
- in 4 days
+
+ in 4 days
+
diff --git a/src/utils/eslint-test-noGoogleTranslateCrashingSyntax.tsx b/src/utils/eslint-test-noGoogleTranslateCrashingSyntax.tsx
index c2ba7ddd10..a95f5a1435 100644
--- a/src/utils/eslint-test-noGoogleTranslateCrashingSyntax.tsx
+++ b/src/utils/eslint-test-noGoogleTranslateCrashingSyntax.tsx
@@ -6,6 +6,7 @@
* these work like "expect eslint error".
*/
/* eslint-disable @typescript-eslint/no-unused-vars */
+/* eslint-disable react-refresh/only-export-components */
const condition = true
const bad = 'bad'
@@ -194,6 +195,29 @@ const textAfterTernaryOperator = (
)
+function TextInFragment() {
+ const Arrow1 = () => <>{condition && good}>
+ /* eslint-disable-next-line no-restricted-syntax */
+ const Arrow2 = () => <>{!condition ? good : 'bad'}>
+
+ return (
+ <>
+ {/* good */}
+ {condition ? : }
+ {condition && good}
+ {!condition ? good : good}
+ {/* eslint-disable-next-line no-restricted-syntax */}
+ {!condition ? good : 'bad'}
+ {/* eslint-disable-next-line no-restricted-syntax */}
+ {'bad'}
+ {/* eslint-disable-next-line no-restricted-syntax */}
+ {bad}
+ {/* eslint-disable-next-line no-restricted-syntax */}
+ {bad.toString()}
+ >
+ )
+}
+
const nestedConditionsAreNotSupported = (