diff --git a/.jscodeshift/file/lingui-v5.js b/.jscodeshift/file/lingui-v5.js new file mode 100644 index 0000000000..81daf6f018 --- /dev/null +++ b/.jscodeshift/file/lingui-v5.js @@ -0,0 +1,236 @@ +/** + * Codemod to update Lingui's useLingui() hook to use v5. + * + * Before: + * import {msg} from '@lingui/core/macro' + * import {useLingui} from '@lingui/react' + * const {_} = useLingui() + * _(msg`Lorem ipsum`) + * + * After: + * import {useLingui} from '@lingui/react/macro' + * const {t: l} = useLingui() + * l`Lorem ipsum` + * + * Usage: jscodeshift -t .jscodeshift/file/lingui-v5.js + * Example: jscodeshift -t .jscodeshift/file/lingui-v5.js modules/bottom-sheet/src/lib/Portal.tsx + */ + +/* eslint-disable */ + +export const parser = 'tsx' + +export default function transformer(file, api) { + const j = api.jscodeshift + + const root = j(file.source) + + // Remove msg imports, including aliased imports + root + .find(j.ImportDeclaration) + .filter(path => path.node.source.value === '@lingui/core/macro') + .forEach(path => { + const newSpecifiers = path.node.specifiers.filter(specifier => { + if (j.ImportSpecifier.check(specifier)) { + const importedName = specifier.imported + ? specifier.imported.name + : specifier.local.name + const localName = specifier.local + ? specifier.local.name + : importedName + + return !( + importedName === 'msg' && + (localName === 'msg' || localName === 'msgLingui') + ) + } + return true + }) + + if (newSpecifiers.length > 0) { + path.node.specifiers = newSpecifiers + } else { + j(path).remove() + } + }) + + // Update React imports + root + .find(j.ImportDeclaration) + .filter(path => path.node.source.value === '@lingui/react') + .forEach(path => { + path.node.source = j.stringLiteral('@lingui/react/macro') + }) + + // Replace {_} with {t: l} + root + .find(j.VariableDeclarator, { + init: { + type: 'CallExpression', + callee: {name: 'useLingui'}, + }, + }) + .replaceWith(nodePath => { + const {node} = nodePath + for (let i = 0; i < node.id.properties.length; i++) { + if (node.id.properties[i].key.name === '_') { + node.id.properties[i].key = j.identifier('t') + node.id.properties[i].shorthand = false + node.id.properties[i].value = j.identifier('l') + } + } + return node + }) + + // Replace {_: _l} with {t: l} + root + .find(j.VariableDeclarator, { + init: {callee: {name: 'useLingui'}}, + id: { + properties: properties => + properties.some(p => p.key.name === '_' && p.value.name === '_l'), + }, + }) + .forEach(path => { + const prop = path.node.id.properties.find( + p => p.key.name === '_' && p.value.name === '_l', + ) + if (prop) { + prop.key.name = 't' + prop.value.name = 'l' + } + }) + + // Replace _(msg``) with l`` + root + .find(j.CallExpression, { + callee: {name: '_'}, + arguments: [ + { + type: 'TaggedTemplateExpression', + tag: {name: 'msg'}, + }, + ], + }) + .replaceWith(path => { + const quasis = path.node.arguments[0].quasi + + return j.taggedTemplateExpression(j.identifier('l'), quasis) + }) + + // Replace _l(msgLingui``) with l`` + root + .find(j.CallExpression, { + callee: {name: '_l'}, + arguments: [ + { + type: 'TaggedTemplateExpression', + tag: {name: 'msgLingui'}, + }, + ], + }) + .replaceWith(path => { + const quasis = path.node.arguments[0].quasi + + return j.taggedTemplateExpression(j.identifier('l'), quasis) + }) + + // Replace _(msg()) with l() + root + .find(j.CallExpression, { + callee: {name: '_'}, + arguments: [ + { + type: 'CallExpression', + callee: {name: 'msg'}, + }, + ], + }) + .replaceWith(path => { + return j.callExpression(j.identifier('l'), [ + path.node.arguments[0].arguments[0], + ]) + }) + + // Replace _l(msgLingui()) with l() + root + .find(j.CallExpression, { + callee: {name: '_l'}, + arguments: [ + { + type: 'CallExpression', + callee: {name: 'msgLingui'}, + }, + ], + }) + .replaceWith(path => { + return j.callExpression(j.identifier('l'), [ + path.node.arguments[0].arguments[0], + ]) + }) + + // Replace _(plural()) with plural() + root + .find(j.CallExpression, { + callee: { + type: 'Identifier', + name: '_', + }, + }) + .forEach(path => { + const callExpression = path.node + if ( + callExpression.arguments.length === 1 && + callExpression.arguments[0].type === 'CallExpression' + ) { + const innerCall = callExpression.arguments[0] + + if ( + innerCall.callee.type === 'Identifier' && + innerCall.callee.name === 'plural' + ) { + j(path).replaceWith(innerCall) + } + } + }) + + // Update hook dependency arrays + root + .find(j.CallExpression, { + callee: callee => { + if ( + callee.type === 'Identifier' || + callee.type === 'MemberExpression' + ) { + return true + } + if ( + ['useCallback', 'useEffect', 'useMemo'].includes(callee.name) && + callee.type === 'MemberExpression' && + callee.object.type === 'Identifier' && + callee.object.name === 'React' && + callee.property.type === 'Identifier' && + callee.property.name === 'useCallback' + ) { + return true + } + return false + }, + }) + .forEach(path => { + const dependencyArray = path.node.arguments[1] + if (dependencyArray && dependencyArray.type === 'ArrayExpression') { + dependencyArray.elements = dependencyArray.elements.map(element => { + if ( + element.type === 'Identifier' && + (element.name === '_' || element.name === '_l') + ) { + return j.identifier('l') + } + return element + }) + } + }) + + return root.toSource() +} diff --git a/.jscodeshift/react-import.js b/.jscodeshift/repo/react-import.js similarity index 95% rename from .jscodeshift/react-import.js rename to .jscodeshift/repo/react-import.js index e63238f862..a6a7329dd5 100644 --- a/.jscodeshift/react-import.js +++ b/.jscodeshift/repo/react-import.js @@ -9,8 +9,8 @@ * import { useEffect } from 'react' * useEffect(() => {}, []) * - * Usage: jscodeshift -t .jscodeshift/react-import.js - * Example: jscodeshift -t .jscodeshift/react-import.js src/App.native.tsx + * Usage: jscodeshift -t .jscodeshift/repo/react-import.js + * Example: jscodeshift -t .jscodeshift/repo/react-import.js src/App.native.tsx */ /* eslint-disable */ diff --git a/.jscodeshift/toast-v2.js b/.jscodeshift/repo/toast-v2.js similarity index 94% rename from .jscodeshift/toast-v2.js rename to .jscodeshift/repo/toast-v2.js index a60f2da4ab..d2b51d4901 100644 --- a/.jscodeshift/toast-v2.js +++ b/.jscodeshift/repo/toast-v2.js @@ -9,8 +9,8 @@ * import * as Toast from '#/components/Toast' * Toast.show(message, {type: 'error'}) * - * Usage: jscodeshift -t .jscodeshift/toast-v2.js - * Example: jscodeshift -t .jscodeshift/toast-v2.js src/App.native.tsx + * Usage: jscodeshift -t .jscodeshift/repo/toast-v2.js + * Example: jscodeshift -t .jscodeshift/repo/toast-v2.js src/App.native.tsx */ /* eslint-disable */