i18n: removes useTranslate from ESLint rules (#106597)
* i18n: removes useTranslate from eslint rules * chore: remove some more useTranslate
This commit is contained in:
+1
-1
@@ -128,7 +128,7 @@ module.exports = [
|
||||
{
|
||||
group: ['react-i18next', 'i18next'],
|
||||
importNames: ['t'],
|
||||
message: 'Please import useTranslate from @grafana/i18n and use the t function instead',
|
||||
message: 'Please import from @grafana/i18n instead',
|
||||
},
|
||||
{
|
||||
group: ['react-i18next'],
|
||||
|
||||
+14
-86
@@ -7,9 +7,8 @@ const filename = 'public/app/features/some-feature/nested/SomeFile.tsx';
|
||||
const packageName = '@grafana/i18n';
|
||||
|
||||
const TRANS_IMPORT = `import { Trans } from '${packageName}';`;
|
||||
const T_IMPORT = `import { t } from '${packageName}/internal';`;
|
||||
const USE_TRANSLATE_IMPORT = `import { useTranslate } from '${packageName}';`;
|
||||
const TRANS_AND_USE_TRANSLATE_IMPORT = `import { Trans, useTranslate } from '${packageName}';`;
|
||||
const T_IMPORT = `import { t } from '${packageName}';`;
|
||||
const TRANS_AND_T_IMPORT = `import { Trans, t } from '${packageName}';`;
|
||||
|
||||
const ruleTester = new RuleTester({
|
||||
languageOptions: {
|
||||
@@ -330,7 +329,7 @@ const Foo = () => <div><TestingComponent someProp={<><Trans i18nKey="some-featur
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Fixes basic prop case and adds useTranslate',
|
||||
name: 'Fixes basic prop case',
|
||||
code: `
|
||||
const Foo = () => {
|
||||
const fooBar = 'a';
|
||||
@@ -346,10 +345,9 @@ const Foo = () => {
|
||||
{
|
||||
messageId: 'wrapWithT',
|
||||
output: `
|
||||
${USE_TRANSLATE_IMPORT}
|
||||
${T_IMPORT}
|
||||
const Foo = () => {
|
||||
const { t } = useTranslate();
|
||||
const fooBar = 'a';
|
||||
const fooBar = 'a';
|
||||
return (
|
||||
<div title={t("some-feature.foo.title-foo", "foo")} />
|
||||
)
|
||||
@@ -429,10 +427,9 @@ const Foo = () => {
|
||||
{
|
||||
messageId: 'wrapWithT',
|
||||
output: `
|
||||
${TRANS_AND_USE_TRANSLATE_IMPORT}
|
||||
${TRANS_AND_T_IMPORT}
|
||||
const Foo = () => {
|
||||
const { t } = useTranslate();
|
||||
return (
|
||||
return (
|
||||
<div title={t("some-feature.foo.title-foo", "foo")} />
|
||||
)
|
||||
}`,
|
||||
@@ -469,36 +466,6 @@ const Foo = () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Fixes correctly when useTranslate already exists',
|
||||
code: `
|
||||
${USE_TRANSLATE_IMPORT}
|
||||
const Foo = () => {
|
||||
const { t } = useTranslate();
|
||||
return (
|
||||
<div title="foo" />
|
||||
)
|
||||
}`,
|
||||
filename,
|
||||
errors: [
|
||||
{
|
||||
messageId: 'noUntranslatedStringsProp',
|
||||
suggestions: [
|
||||
{
|
||||
messageId: 'wrapWithT',
|
||||
output: `
|
||||
${USE_TRANSLATE_IMPORT}
|
||||
const Foo = () => {
|
||||
const { t } = useTranslate();
|
||||
return (
|
||||
<div title={t("some-feature.foo.title-foo", "foo")} />
|
||||
)
|
||||
}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Fixes and uses ID from attribute if exists',
|
||||
@@ -563,41 +530,6 @@ const Foo = () => <div title={t("some-feature.foo.title-foo", "foo")} />`,
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Fixes correctly when useTranslate import already exists',
|
||||
code: `
|
||||
${USE_TRANSLATE_IMPORT}
|
||||
const Foo = () => {
|
||||
const { t } = useTranslate();
|
||||
return (<>
|
||||
<div title={t("some-feature.foo.title-foo", "foo")} />
|
||||
<div title={"bar"} />
|
||||
</>)
|
||||
}
|
||||
`,
|
||||
filename,
|
||||
errors: [
|
||||
{
|
||||
messageId: 'noUntranslatedStringsProp',
|
||||
suggestions: [
|
||||
{
|
||||
messageId: 'wrapWithT',
|
||||
output: `
|
||||
${USE_TRANSLATE_IMPORT}
|
||||
const Foo = () => {
|
||||
const { t } = useTranslate();
|
||||
return (<>
|
||||
<div title={t("some-feature.foo.title-foo", "foo")} />
|
||||
<div title={t("some-feature.foo.title-bar", "bar")} />
|
||||
</>)
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Fixes correctly when no return statement',
|
||||
code: `
|
||||
@@ -637,8 +569,7 @@ const Foo = () => <div title="foo" />`,
|
||||
{
|
||||
messageId: 'wrapWithT',
|
||||
output: `
|
||||
${T_IMPORT}
|
||||
${TRANS_IMPORT}
|
||||
${TRANS_AND_T_IMPORT}
|
||||
const Foo = () => <div title={t("some-feature.foo.title-foo", "foo")} />`,
|
||||
},
|
||||
],
|
||||
@@ -787,10 +718,9 @@ const Foo = () => {
|
||||
{
|
||||
messageId: 'wrapWithT',
|
||||
output: `
|
||||
${USE_TRANSLATE_IMPORT}
|
||||
${T_IMPORT}
|
||||
const Foo = () => {
|
||||
const { t } = useTranslate();
|
||||
const thing = {
|
||||
const thing = {
|
||||
label: t(\"some-feature.foo.thing.label.test\", \"test\"),
|
||||
}
|
||||
|
||||
@@ -893,10 +823,9 @@ const Foo = () => {
|
||||
filename,
|
||||
options: [{ forceFix: ['public/app/features/some-feature'] }],
|
||||
output: `
|
||||
${USE_TRANSLATE_IMPORT}
|
||||
${T_IMPORT}
|
||||
const Foo = () => {
|
||||
const { t } = useTranslate();
|
||||
return <div title={t("some-feature.foo.title-foo", "foo")} />
|
||||
return <div title={t("some-feature.foo.title-foo", "foo")} />
|
||||
}`,
|
||||
errors: [
|
||||
{
|
||||
@@ -905,10 +834,9 @@ return <div title={t("some-feature.foo.title-foo", "foo")} />
|
||||
{
|
||||
messageId: 'wrapWithT',
|
||||
output: `
|
||||
${USE_TRANSLATE_IMPORT}
|
||||
${T_IMPORT}
|
||||
const Foo = () => {
|
||||
const { t } = useTranslate();
|
||||
return <div title={t("some-feature.foo.title-foo", "foo")} />
|
||||
return <div title={t("some-feature.foo.title-foo", "foo")} />
|
||||
}`,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -98,11 +98,10 @@ function canBeFixed(node, context) {
|
||||
}
|
||||
|
||||
// If we're going to try and fix using `t`, and it already exists in the scope,
|
||||
// but not from `useTranslate`, then we can't fix/provide a suggestion
|
||||
// then we can't fix/provide a suggestion
|
||||
if (isPropertyOrAttribute && parentMethod) {
|
||||
const hasTDeclaration = getTDeclaration(parentMethod, context);
|
||||
const hasUseTranslateDeclaration = methodHasUseTranslate(parentMethod, context);
|
||||
if (hasTDeclaration && !hasUseTranslateDeclaration) {
|
||||
if (hasTDeclaration) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -135,6 +134,15 @@ function canBeFixed(node, context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given node, check the scope and find a variable declaration of `t`
|
||||
* @param {Node} node
|
||||
* @param {RuleContextWithOptions} context
|
||||
*/
|
||||
function getTDeclaration(node, context) {
|
||||
return context.sourceCode.getScope(node).variables.find((v) => v.name === 't');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translation prefix from the filename
|
||||
* @param {RuleContextWithOptions} context
|
||||
@@ -233,44 +241,11 @@ function getComponentNames(node, context) {
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given node, check the scope and find a variable declaration of `t`
|
||||
* @param {Node} node
|
||||
* @param {RuleContextWithOptions} context
|
||||
*/
|
||||
function getTDeclaration(node, context) {
|
||||
return context.sourceCode.getScope(node).variables.find((v) => v.name === 't');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a node has a variable declaration of `t`
|
||||
* that came from a `useTranslate` call
|
||||
* @param {Node} node The node
|
||||
* @param {RuleContextWithOptions} context
|
||||
*/
|
||||
function methodHasUseTranslate(node, context) {
|
||||
const tDeclaration = getTDeclaration(node, context);
|
||||
return (
|
||||
tDeclaration &&
|
||||
tDeclaration.defs.find((definition) => {
|
||||
const isVariableDeclaration = definition.node.type === AST_NODE_TYPES.VariableDeclarator;
|
||||
const declarationInit = isVariableDeclaration ? definition.node.init : null;
|
||||
return (
|
||||
isVariableDeclaration &&
|
||||
declarationInit &&
|
||||
declarationInit.type === AST_NODE_TYPES.CallExpression &&
|
||||
declarationInit.callee.type === AST_NODE_TYPES.Identifier &&
|
||||
declarationInit.callee.name === 'useTranslate'
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the import fixer for a node
|
||||
* @param {JSXElement|JSXFragment|JSXAttribute|Property} node
|
||||
* @param {RuleFixer} fixer The fixer
|
||||
* @param {'Trans'|'t'|'useTranslate'} importName The member to import from either `@grafana/i18n` or `@grafana/i18n/internal`
|
||||
* @param {'Trans'|'t'} importName The member to import from `@grafana/i18n`
|
||||
* @param {RuleContextWithOptions} context
|
||||
* @returns {import('@typescript-eslint/utils/ts-eslint').RuleFix|undefined} The fix
|
||||
*/
|
||||
@@ -280,22 +255,9 @@ function getImportsFixer(node, fixer, importName, context) {
|
||||
/** Map of where we expect to import each translation util from */
|
||||
const importPackage = {
|
||||
Trans: '@grafana/i18n',
|
||||
useTranslate: '@grafana/i18n',
|
||||
t: '@grafana/i18n/internal',
|
||||
t: '@grafana/i18n',
|
||||
};
|
||||
|
||||
const parentMethod = getParentMethod(node, context);
|
||||
|
||||
if (importName === 't') {
|
||||
// If we're trying to import `t`,
|
||||
// and there's already a `t` variable declaration in the parent method that came from `useTranslate`,
|
||||
// do nothing
|
||||
const declarationFromUseTranslate = parentMethod ? methodHasUseTranslate(parentMethod, context) : false;
|
||||
if (declarationFromUseTranslate) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const expectedImport = importPackage[importName];
|
||||
|
||||
const existingAppCoreI18n = body.find(
|
||||
@@ -354,73 +316,6 @@ const getTransFixers = (node, context) => (fixer) => {
|
||||
return fixes;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
*/
|
||||
const firstCharIsUpper = (str) => {
|
||||
return str.charAt(0) === str.charAt(0).toUpperCase();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {JSXAttribute|Property} node
|
||||
* @param {RuleFixer} fixer
|
||||
* @param {RuleContextWithOptions} context
|
||||
* @returns {import('@typescript-eslint/utils/ts-eslint').RuleFix|undefined} The fix
|
||||
*/
|
||||
const getUseTranslateFixer = (node, fixer, context) => {
|
||||
const parentMethod = getParentMethod(node, context);
|
||||
|
||||
const functionIsNotUpperCase =
|
||||
parentMethod &&
|
||||
parentMethod.type === AST_NODE_TYPES.FunctionDeclaration &&
|
||||
(!parentMethod.id || !firstCharIsUpper(parentMethod.id.name));
|
||||
|
||||
const variableDeclaratorIsNotUpperCase =
|
||||
parentMethod &&
|
||||
parentMethod.parent.type === AST_NODE_TYPES.VariableDeclarator &&
|
||||
parentMethod.parent.id.type === AST_NODE_TYPES.Identifier &&
|
||||
!firstCharIsUpper(parentMethod.parent.id.name);
|
||||
|
||||
// If the node is not within a function, or the parent method does not start with an uppercase letter,
|
||||
// then we can't reliably add `useTranslate`, as this may not be a React component
|
||||
if (
|
||||
!parentMethod ||
|
||||
functionIsNotUpperCase ||
|
||||
variableDeclaratorIsNotUpperCase ||
|
||||
parentMethod.body.type !== AST_NODE_TYPES.BlockStatement
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const returnStatement = parentMethod.body.body.find((node) => node.type === AST_NODE_TYPES.ReturnStatement);
|
||||
if (!returnStatement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const returnStatementIsJsx =
|
||||
returnStatement.argument &&
|
||||
(returnStatement.argument.type === AST_NODE_TYPES.JSXElement ||
|
||||
returnStatement.argument.type === AST_NODE_TYPES.JSXFragment);
|
||||
|
||||
if (!returnStatementIsJsx) {
|
||||
return;
|
||||
}
|
||||
const tDeclarationExists = getTDeclaration(parentMethod, context);
|
||||
const useTranslateExists = methodHasUseTranslate(parentMethod, context);
|
||||
|
||||
if (tDeclarationExists && useTranslateExists) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we've got all this way, then:
|
||||
// - There is a parent method
|
||||
// - It returns JSX
|
||||
// - The method name starts with a capital letter
|
||||
// - There is not already a call to `useTranslate` in the parent method
|
||||
// In that scenario, we assume that we can fix and add a usage of the hook to the start of the body of the method
|
||||
return fixer.insertTextBefore(parentMethod.body.body[0], 'const { t } = useTranslate();\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {JSXAttribute|Property} node
|
||||
* @param {RuleContextWithOptions} context
|
||||
@@ -440,14 +335,7 @@ const getTFixers = (node, context) => (fixer) => {
|
||||
);
|
||||
}
|
||||
|
||||
// Check if we need to add `useTranslate` to the node
|
||||
const useTranslateFixer = getUseTranslateFixer(node, fixer, context);
|
||||
if (useTranslateFixer) {
|
||||
fixes.push(useTranslateFixer);
|
||||
}
|
||||
|
||||
// Check if we need to add `t` or `useTranslate` to the imports
|
||||
const importToAdd = useTranslateFixer ? 'useTranslate' : 't';
|
||||
const importToAdd = 't';
|
||||
const importsFixer = getImportsFixer(node, fixer, importToAdd, context);
|
||||
if (importsFixer) {
|
||||
fixes.push(importsFixer);
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import { render, screen, fireEvent } from '@testing-library/react';
|
||||
|
||||
import { ExtensionToolbarItemButton } from './ExtensionToolbarItemButton';
|
||||
|
||||
// Mock the useTranslate hook
|
||||
// Mock the t function
|
||||
jest.mock('@grafana/i18n', () => ({
|
||||
t: (_: string, fallback: string, values?: Record<string, string>) => {
|
||||
if (values) {
|
||||
|
||||
Reference in New Issue
Block a user