From c5bdbe14c18b6ae87dbece76c7c9d5adf3ce2207 Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 26 Oct 2020 09:31:56 +0100 Subject: [PATCH] feat: introduced a new codemod to remove unused imports once finish --- README.md | 8 ++- bin/__tests__/lingui-codemod.test.ts | 39 ++++++++++-- bin/cli.ts | 49 ++++++++++----- jest.config.js | 2 +- .../remove-unused-imports.input.js | 14 +++++ .../remove-unused-imports.output.js | 14 +++++ .../__tests__/remove-unused-imports.test.ts | 10 ++++ transforms/remove-unused-imports.ts | 60 +++++++++++++++++++ transforms/v2-to-v3.ts | 4 +- 9 files changed, 174 insertions(+), 26 deletions(-) create mode 100644 transforms/__testfixtures__/remove-unused-imports/remove-unused-imports.input.js create mode 100644 transforms/__testfixtures__/remove-unused-imports/remove-unused-imports.output.js create mode 100644 transforms/__tests__/remove-unused-imports.test.ts create mode 100644 transforms/remove-unused-imports.ts diff --git a/README.md b/README.md index 12eb22d..602ccb6 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,11 @@ This repository contains a collection of codemod scripts for use with [JSCodeshi - `transform` - name of transform, see available transforms below. - `path` - files or directory to transform -- use the `--dry` option for a dry-run and use `--print` to print the output for comparison +- use the `--dry` option for a dry-run +- use `--print` to print the output for + comparison +- use `--remove-unused-imports` to remove unused imports once finished the codemod + This will start an interactive wizard, and then run the specified transform. @@ -53,7 +57,7 @@ npx @lingui/codemods --jscodeshift="--printOptions='{\"quote\ A CLI is built-in to help you migrate your codebase, will ask you some questions: ```sh -➜ project git:(master) lingui-codemod +➜ project git:(master) @lingui/codemods ? On which files or directory should the codemods be applied? for ex: ./src ? Which dialect of JavaScript do you use? for ex: JavaScript | Typescript | JavaScript with Flow ? Which transform would you like to apply? for ex: `v2-to-v3` diff --git a/bin/__tests__/lingui-codemod.test.ts b/bin/__tests__/lingui-codemod.test.ts index 044edd8..e66f3ec 100644 --- a/bin/__tests__/lingui-codemod.test.ts +++ b/bin/__tests__/lingui-codemod.test.ts @@ -90,7 +90,7 @@ describe('runTransform', () => { }); it('runs jscodeshift for the given transformer', () => { - execaReturnValue = { error: null }; + execaReturnValue = { failed: false }; console.log = jest.fn(); runTransform({ files: 'src', @@ -108,7 +108,7 @@ describe('runTransform', () => { }); it('supports jscodeshift flags', () => { - execaReturnValue = { error: null }; + execaReturnValue = { failed: false }; console.log = jest.fn(); runTransform({ files: 'folder', @@ -126,7 +126,7 @@ describe('runTransform', () => { }); it('supports typescript parser', () => { - execaReturnValue = { error: null }; + execaReturnValue = { failed: false }; console.log = jest.fn(); runTransform({ files: 'folder', @@ -144,7 +144,7 @@ describe('runTransform', () => { }); it('supports jscodeshift custom arguments', () => { - execaReturnValue = { error: null }; + execaReturnValue = { failed: false }; console.log = jest.fn(); runTransform({ files: 'folder', @@ -164,9 +164,38 @@ describe('runTransform', () => { ); }); + it('supports remove-unused-imports flag that runs a codemod to clean all unused imports', () => { + execaReturnValue = { failed: false }; + console.log = jest.fn(); + runTransform({ + files: 'folder', + flags: { + removeUnusedImports: true, + }, + parser: 'babel', + transformer: 'v2-to-v3' + }); + expect(console.log).toBeCalledTimes(2) + // @ts-ignore + expect(console.log.mock.calls).toEqual([ + [ + `Executing command: jscodeshift --verbose=2 --ignore-pattern=**/node_modules/** --parser babel --extensions=jsx,js --transform ${path.join( + transformerDirectory, + 'v2-to-v3.js' + )} folder` + ], + [ + `Executing command: jscodeshift --verbose=2 --ignore-pattern=**/node_modules/** --parser babel --extensions=jsx,js --transform ${path.join( + transformerDirectory, + 'remove-unused-imports.js' + )} folder` + ] + ]); + }); + it('rethrows jscodeshift errors', () => { const transformerError = new Error('bum'); - execaReturnValue = { error: transformerError }; + execaReturnValue = { failed: true, stderr: transformerError }; console.log = jest.fn(); expect(() => { runTransform({ diff --git a/bin/cli.ts b/bin/cli.ts index f825ba5..10e13a6 100755 --- a/bin/cli.ts +++ b/bin/cli.ts @@ -48,12 +48,12 @@ type TransformOpts = { [string: string]: any } -function runTransform({ files, flags, parser, transformer, answers }: TransformOpts) { +function runTransform({ files, flags, parser, transformer }: TransformOpts) { const transformerPath = path.join(transformerDirectory, `${transformer}.js`); let args = []; - const { dry, print, explicitRequire } = flags; + const { dry, print, removeUnusedImports } = flags; if (dry) { args.push('--dry'); @@ -62,10 +62,6 @@ function runTransform({ files, flags, parser, transformer, answers }: TransformO args.push('--print'); } - if (explicitRequire === 'false') { - args.push('--explicit-require=false'); - } - args.push('--verbose=2'); args.push('--ignore-pattern=**/node_modules/**'); @@ -93,10 +89,31 @@ function runTransform({ files, flags, parser, transformer, answers }: TransformO stripFinalNewline: false }); - // @ts-ignore - if (result.error) { - // @ts-ignore - throw result.error; + if (removeUnusedImports) { + let newArgs: any[] = args.filter(el => { + return el !== "--transform" && el !== transformerPath && el !== files + }); + newArgs = newArgs.concat(['--transform', path.join(transformerDirectory, `remove-unused-imports.js`)]); + newArgs = newArgs.concat(files); + + console.log(`Executing command: jscodeshift ${newArgs.join(' ')}`); + + const removeUnusedResult = execa.sync( + jscodeshiftExecutable, + newArgs, + { + stdio: 'inherit', + stripFinalNewline: false + } + ); + + if (removeUnusedResult.failed) { + throw removeUnusedResult.stderr; + } + } + + if (result.failed) { + throw result.stderr; } } @@ -141,15 +158,15 @@ function run() { transform One of the choices from https://github.com/@lingui/packages/codemods path Files or directory to transform. Can be a glob like src/**.test.js Options - --force Bypass Git safety checks and forcibly run codemods - --dry Dry run (no changes are made to files) - --print Print transformed files to your terminal - --explicit-require Transform only if React is imported in the file (default: true) - --jscodeshift (Advanced) Pass options directly to jscodeshift + --force Bypass Git safety checks and forcibly run codemods + --dry Dry run (no changes are made to files) + --remove-unused-imports Remove unused imports once finished the codemod + --print Print transformed files to your terminal + --jscodeshift (Advanced) Pass options directly to jscodeshift ` }, { - boolean: ['force', 'dry', 'print', 'explicit-require', 'help'], + boolean: ['force', 'dry', 'print', 'remove-unused', 'help'], string: ['_'], alias: { h: 'help' diff --git a/jest.config.js b/jest.config.js index d9ea5ab..7dcf05f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,6 +2,6 @@ module.exports = { verbose: true, roots: ["/transforms", "/bin"], transform: { - "^.+\\.tsx?$": "ts-jest" + "^.+\\.ts?$": "ts-jest" } }; \ No newline at end of file diff --git a/transforms/__testfixtures__/remove-unused-imports/remove-unused-imports.input.js b/transforms/__testfixtures__/remove-unused-imports/remove-unused-imports.input.js new file mode 100644 index 0000000..71595d9 --- /dev/null +++ b/transforms/__testfixtures__/remove-unused-imports/remove-unused-imports.input.js @@ -0,0 +1,14 @@ +import { React, core } from "react"; +import { date, number } from "@lingui/core"; +import { t } from "@lingui/macro"; + +export default () => { + const [gi, setgi] = React.useState(false) + return ( + <> + {t`Some example`} + {date(new Date())} +
hola
+ + ) +} \ No newline at end of file diff --git a/transforms/__testfixtures__/remove-unused-imports/remove-unused-imports.output.js b/transforms/__testfixtures__/remove-unused-imports/remove-unused-imports.output.js new file mode 100644 index 0000000..902d143 --- /dev/null +++ b/transforms/__testfixtures__/remove-unused-imports/remove-unused-imports.output.js @@ -0,0 +1,14 @@ +import { React } from "react"; +import { date } from "@lingui/core"; +import { t } from "@lingui/macro"; + +export default () => { + const [gi, setgi] = React.useState(false) + return ( + <> + {t`Some example`} + {date(new Date())} +
hola
+ + ) +} \ No newline at end of file diff --git a/transforms/__tests__/remove-unused-imports.test.ts b/transforms/__tests__/remove-unused-imports.test.ts new file mode 100644 index 0000000..9e0bfcb --- /dev/null +++ b/transforms/__tests__/remove-unused-imports.test.ts @@ -0,0 +1,10 @@ +import { defineTest } from "jscodeshift/dist/testUtils"; + +describe("We remove all the unused imports", () => { + defineTest( + __dirname, + "remove-unused-imports", + null, + "remove-unused-imports/remove-unused-imports", + ); +}); \ No newline at end of file diff --git a/transforms/remove-unused-imports.ts b/transforms/remove-unused-imports.ts new file mode 100644 index 0000000..3d40378 --- /dev/null +++ b/transforms/remove-unused-imports.ts @@ -0,0 +1,60 @@ +import { Transform } from "jscodeshift"; + +const transform: Transform = (fileInfo, api, options) => { + const j = api.jscodeshift; + const root = j(fileInfo.source); + + const filterAndTransformRequires = path => { + const specifiers = path.value.specifiers; + const parentScope = j(path).closestScope(); + return ( + specifiers.filter(importPath => { + const varName = importPath.local.name; + const requireName = importPath.imported + ? importPath.imported.name + : importPath.local.name; + const scopeNode = path.scope.node; + + // We need this to make sure the JSX transform can use `React` + if (requireName === "React") { + return false; + } + + // console.debug("parsing require named ", requireName); + // Remove required vars that aren't used. + const identifierUsages = parentScope + .find(j.Identifier, { name: varName }) + // Ignore require vars + .filter(identifierPath => identifierPath.parentPath.value !== importPath); + const decoratorUsages = parentScope.find(j.ClassDeclaration).filter((it: any) => { + return ( + (it.value.decorators || []).filter( + decorator => decorator.expression.name === varName + ).length > 0 + ); + }); + + if (!identifierUsages.size() && !decoratorUsages.size()) { + path.value.specifiers = path.value.specifiers.filter( + it => (it === importPath) === false + ); + if (path.value.specifiers.length === 0) { + j(path).remove(); + } + return true; + } + }).length > 0 + ); + }; + + const didTransform = + root + .find(j.ImportDeclaration) + .filter(filterAndTransformRequires) + .size() > 0; + + return didTransform ? root.toSource(options.printOptions) : null; +}; + + +export default transform; \ No newline at end of file diff --git a/transforms/v2-to-v3.ts b/transforms/v2-to-v3.ts index d6a1be5..cb2a0e0 100644 --- a/transforms/v2-to-v3.ts +++ b/transforms/v2-to-v3.ts @@ -1,6 +1,6 @@ import { Transform, JSCodeshift, Collection } from "jscodeshift"; -const transform: Transform = (fileInfo, api) => { +const transform: Transform = (fileInfo, api, options) => { const j = api.jscodeshift; const root = j(fileInfo.source); @@ -11,7 +11,7 @@ const transform: Transform = (fileInfo, api) => { changeFromMacroToCore(root, j) pluralPropsChanges(root, j) - return root.toSource(); + return root.toSource(options.printOptions); }; export default transform;