From 76278e2b7cc81bbc58b626741cd3d144ade657be Mon Sep 17 00:00:00 2001 From: Juanra GM Date: Thu, 27 Oct 2022 00:51:28 +0200 Subject: [PATCH] feat(codemod): add `suid-imports` action --- .changeset/cyan-needles-double.md | 5 ++ packages/codemod/src/actions/suidImports.ts | 52 +++++++++++++++ packages/codemod/src/bin.ts | 12 ++++ .../src/transforms/transformSuidImports.ts | 63 +++++++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 .changeset/cyan-needles-double.md create mode 100644 packages/codemod/src/actions/suidImports.ts create mode 100644 packages/codemod/src/transforms/transformSuidImports.ts diff --git a/.changeset/cyan-needles-double.md b/.changeset/cyan-needles-double.md new file mode 100644 index 000000000..d1fe43bea --- /dev/null +++ b/.changeset/cyan-needles-double.md @@ -0,0 +1,5 @@ +--- +"@suid/codemod": minor +--- + +Add `suid-imports` action diff --git a/packages/codemod/src/actions/suidImports.ts b/packages/codemod/src/actions/suidImports.ts new file mode 100644 index 000000000..c21f38137 --- /dev/null +++ b/packages/codemod/src/actions/suidImports.ts @@ -0,0 +1,52 @@ +import transformSuidImports from "../transforms/transformSuidImports"; +import applyTransforms from "../utils/applyTransforms.js"; +import colorize from "../utils/colorize.js"; +import fg from "fast-glob"; +import { readFile, writeFile } from "fs/promises"; +import micromatch from "micromatch"; +import { normalize, relative } from "path"; + +export default async function suidImports(options: { + cwd: string; + filters: string[]; + write: boolean; +}) { + const inputPath = normalize(options.cwd); + const inPattern = inputPath.replaceAll("\\", "/") + "/**/*"; + const entries = fg.stream(inPattern, { + dot: true, + ignore: ["**/node_modules/**"], + }); + + let files = 0; + for await (const entry of entries) { + const path = normalize(entry.toString()); + const validExt = /\.[jt]sx?$/.test(path); + if (!validExt) continue; + + const rpath = relative(inputPath, path); + const matches = + !options.filters.length || !!micromatch([rpath], options.filters).length; + + if (!matches) continue; + + console.log(`+ ${colorize(rpath)}`); + files++; + + const contents = (await readFile(path)).toString(); + let imports = 0; + const newContents = applyTransforms(contents, [ + async (source) => { + return await transformSuidImports(source, { + format: "named", + onImport(module) { + imports++; + console.log(`# ${colorize(module, "yellow")} `); + }, + }); + }, + ]); + if (options.write && imports) await writeFile(path, newContents); + } + if (!files) throw new Error(`No files found`); +} diff --git a/packages/codemod/src/bin.ts b/packages/codemod/src/bin.ts index 86831d025..3a3991b00 100644 --- a/packages/codemod/src/bin.ts +++ b/packages/codemod/src/bin.ts @@ -2,6 +2,7 @@ import fixEsm from "./actions/fixEsm"; import mui2suid from "./actions/mui2suid"; import react2solid from "./actions/react2solid"; +import suidImports from "./actions/suidImports"; import muiVersion from "./utils/muiVersion"; import { program } from "commander"; import { readFileSync } from "fs"; @@ -110,4 +111,15 @@ program write: boolean; }) => fixEsm(options) ); + +program + .command("suid-imports") + .description("Transforms the SUID imports.") + .requiredOption("-f,--filters [glob patterns]", "Filters by glob patterns") + .option("--format [value]", "Output import format (values: named)", "named") + .option("--cwd [path]", "Current working directory path", process.cwd()) + .option("-w,--write", "Overwrites the files with the transformed code") + .action((options: { cwd: string; filters: string[]; write: boolean }) => + suidImports(options) + ); program.parse(process.argv); diff --git a/packages/codemod/src/transforms/transformSuidImports.ts b/packages/codemod/src/transforms/transformSuidImports.ts new file mode 100644 index 000000000..8a7460083 --- /dev/null +++ b/packages/codemod/src/transforms/transformSuidImports.ts @@ -0,0 +1,63 @@ +import type { SourceFile } from "ts-morph"; + +function createImportsMap() { + const map: Record> = {}; + + return { + map, + add(module: string, named: string, alias: string) { + if (!map[module]) map[module] = {}; + map[module][named] = alias; + }, + }; +} + +export default async function transformSuidImports( + source: SourceFile, + options: { + format: "named"; + onImport?: (moduleSpecifier: string) => false | void; + dryRun?: boolean; + } +) { + const allImports = source.getImportDeclarations(); + const imports = createImportsMap(); + + for (const node of allImports) { + const alias = node.getDefaultImport()?.getText(); + const moduleSpecifier = node.getModuleSpecifier()?.getLiteralText(); + if (!alias || !moduleSpecifier) continue; + const levels = moduleSpecifier.split("/"); + const [importScope, importModule, importPath] = levels; + + if (importScope === "@suid" && levels.length === 3) { + if (importModule === "material") { + imports.add("@suid/material", importPath, alias); + if (options.onImport) { + if (options.onImport(moduleSpecifier) !== false) { + if (!node.getNamedImports().length) { + node.remove(); + } else { + node.removeDefaultImport(); + } + } + } + } + } + } + + if (!options.dryRun) + for (const moduleSpecifier in imports.map) { + const importNode = source.addImportDeclaration({ + moduleSpecifier, + }); + const names = Object.keys(imports.map[moduleSpecifier]).sort(); + for (const name of names) { + const alias = imports.map[moduleSpecifier][name]; + importNode.addNamedImport({ + name, + alias: name === alias ? undefined : alias, + }); + } + } +}