From 0622983f4c4dec73fcea75351db73f5b56b7f973 Mon Sep 17 00:00:00 2001 From: lukaw3d Date: Thu, 20 Jun 2024 22:08:58 +0200 Subject: [PATCH] Lint rule to detect broken Google Translate in CommissionBounds The reason d85c5ffdaf89029fe666086d769402370fe86144 was needed is: - `CommissionBounds` returned <>string - `details ? : ` conditionally inserted it --- .changelog/1984.internal.md | 1 + .eslintrc.js | 15 ++++++++++++ src/app/components/DateFormatter/index.tsx | 2 +- src/app/components/ErrorFormatter/index.tsx | 2 +- .../ImportAccountsStepFormatter/index.tsx | 2 +- src/app/components/TimeToEpoch/index.tsx | 2 +- .../Transaction/__tests__/index.test.tsx | 2 +- .../__snapshots__/index.test.tsx.snap | 4 +++- .../DebondingDelegationList.test.tsx.snap | 4 +++- ...t-test-noGoogleTranslateCrashingSyntax.tsx | 24 +++++++++++++++++++ 10 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 .changelog/1984.internal.md 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 = (