diff --git a/bin/cli.ts b/bin/cli.ts index 589da07..e6cab28 100755 --- a/bin/cli.ts +++ b/bin/cli.ts @@ -122,6 +122,10 @@ const TRANSFORMER_INQUIRER_CHOICES = [ name: "v2-to-v3: Migrates v2 standards to the new standards of @lingui v3.X.X", value: "v2-to-v3", }, + { + name: "v5: Split @lingui/macro imports to specific packages @lingui/react/macro and @lingui/core/macro", + value: "split-macro-imports", + }, ]; const PARSER_INQUIRER_CHOICES = [ diff --git a/transforms/__tests__/split-macro-imports.test.ts b/transforms/__tests__/split-macro-imports.test.ts new file mode 100644 index 0000000..235a25f --- /dev/null +++ b/transforms/__tests__/split-macro-imports.test.ts @@ -0,0 +1,72 @@ +import { defineInlineTest } from "jscodeshift/dist/testUtils"; +import transformer from "../split-macro-imports"; + +describe("basic", () => { + defineInlineTest( + transformer, + {}, + ` +import { + // core + t, + plural, + selectOrdinal, + select, + defineMessage, + msg, + // react + Trans, + Plural, + SelectOrdinal, + Select, + useLingui, +} from "@lingui/macro"; +`, + ` +import { Trans, Plural, SelectOrdinal, Select, useLingui } from "@lingui/react/macro"; +import { t, plural, selectOrdinal, select, defineMessage, msg } from "@lingui/core/macro"; +`, + ); +}); + +describe("Local identifiers are renamed", () => { + defineInlineTest( + transformer, + {}, + `import { t as _t, Trans as _Trans } from '@lingui/macro';`, + ` +import { Trans as _Trans } from "@lingui/react/macro"; +import { t as _t } from "@lingui/core/macro"; +`, + ); +}); + +describe("Multiple imports", () => { + defineInlineTest( + transformer, + {}, + ` +import { t, Trans } from '@lingui/macro'; +import { plural, Plural } from '@lingui/macro'; +`, + ` +import { Trans, Plural } from "@lingui/react/macro"; +import { t, plural } from "@lingui/core/macro"; +`, + ); +}); + +describe("Existing imports", () => { + defineInlineTest( + transformer, + {}, + ` +import { useLingui } from '@lingui/react/macro'; +import { t, Trans } from '@lingui/macro'; +`, + ` +import { t } from "@lingui/core/macro"; +import { useLingui, Trans } from '@lingui/react/macro'; +`, + ); +}); diff --git a/transforms/__tests__/v2-to-v3.test.ts b/transforms/__tests__/v2-to-v3.test.ts index a8f2243..09d7211 100644 --- a/transforms/__tests__/v2-to-v3.test.ts +++ b/transforms/__tests__/v2-to-v3.test.ts @@ -1,4 +1,4 @@ -import { defineTest } from "jscodeshift/dist/testUtils"; +import { defineTest, defineInlineTest } from "jscodeshift/dist/testUtils"; describe("Plural props changed from an object, to a (value, object)", () => { defineTest(__dirname, "v2-to-v3", null, "v2-to-v3/plural"); diff --git a/transforms/split-macro-imports.ts b/transforms/split-macro-imports.ts new file mode 100644 index 0000000..e758fcf --- /dev/null +++ b/transforms/split-macro-imports.ts @@ -0,0 +1,92 @@ +import { API, FileInfo, JSCodeshift, ImportSpecifier } from "jscodeshift"; + +// Define the sets of symbols for each new package +const coreMacroSymbols = new Set([ + "ChoiceOptions", + "t", + "plural", + "selectOrdinal", + "select", + "defineMessage", + "msg", +]); + +const reactMacroSymbols = new Set([ + "Trans", + "Plural", + "SelectOrdinal", + "Select", + "useLingui", +]); + +// Helper function to determine the new package for a symbol +const getNewPackageForSymbol = (symbol: string): string | null => { + if (coreMacroSymbols.has(symbol)) return "@lingui/core/macro"; + if (reactMacroSymbols.has(symbol)) return "@lingui/react/macro"; + return null; +}; + +// Helper function to add or amend an import specifier +const addOrAmendImport = ( + j: JSCodeshift, + root: ReturnType, + packageName: string, + specifier: ImportSpecifier, +) => { + const existingImport = root.find(j.ImportDeclaration, { + source: { value: packageName }, + }); + + if (existingImport.size() === 0) { + root + .get() + .node.program.body.unshift( + j.importDeclaration([specifier], j.literal(packageName)), + ); + } else { + existingImport.get().node.specifiers.push(specifier); + } +}; + +export default function transformer(file: FileInfo, api: API) { + const j = api.jscodeshift; + const root = j(file.source); + + // Find existing imports from '@lingui/macro' + const importedDeclarations = root.find(j.ImportDeclaration, { + source: { value: "@lingui/macro" }, + }); + + if (importedDeclarations.size() === 0) { + return file.source; // No changes needed if there's no import from '@lingui/macro' + } + + // Process each import declaration + importedDeclarations.forEach((path) => { + const importDecl = path.value; + + if (importDecl.specifiers && importDecl.specifiers.length) { + importDecl.specifiers.forEach((specifier) => { + if (specifier.type === "ImportSpecifier") { + const importedName = specifier.imported.name; + const localName = specifier.local.name; + const newPackage = getNewPackageForSymbol(importedName); + + if (newPackage) { + // Create a new import specifier with the local name + const newSpecifier = j.importSpecifier( + j.identifier(importedName), + j.identifier(localName), + ); + addOrAmendImport(j, root, newPackage, newSpecifier); + } + } + }); + } + }); + + // Remove the old import declaration + importedDeclarations.remove(); + + return root.toSource(); +}