Skip to content

Commit

Permalink
feat: introduced a new codemod to remove unused imports once finish
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergio committed Oct 26, 2020
1 parent a56f44d commit c5bdbe1
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 26 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -53,7 +57,7 @@ npx @lingui/codemods <transform> <path> --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`
Expand Down
39 changes: 34 additions & 5 deletions bin/__tests__/lingui-codemod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -108,7 +108,7 @@ describe('runTransform', () => {
});

it('supports jscodeshift flags', () => {
execaReturnValue = { error: null };
execaReturnValue = { failed: false };
console.log = jest.fn();
runTransform({
files: 'folder',
Expand All @@ -126,7 +126,7 @@ describe('runTransform', () => {
});

it('supports typescript parser', () => {
execaReturnValue = { error: null };
execaReturnValue = { failed: false };
console.log = jest.fn();
runTransform({
files: 'folder',
Expand All @@ -144,7 +144,7 @@ describe('runTransform', () => {
});

it('supports jscodeshift custom arguments', () => {
execaReturnValue = { error: null };
execaReturnValue = { failed: false };
console.log = jest.fn();
runTransform({
files: 'folder',
Expand All @@ -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({
Expand Down
49 changes: 33 additions & 16 deletions bin/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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/**');
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ module.exports = {
verbose: true,
roots: ["<rootDir>/transforms", "<rootDir>/bin"],
transform: {
"^.+\\.tsx?$": "ts-jest"
"^.+\\.ts?$": "ts-jest"
}
};
Original file line number Diff line number Diff line change
@@ -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())}
<div>hola</div>
</>
)
}
Original file line number Diff line number Diff line change
@@ -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())}
<div>hola</div>
</>
)
}
10 changes: 10 additions & 0 deletions transforms/__tests__/remove-unused-imports.test.ts
Original file line number Diff line number Diff line change
@@ -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",
);
});
60 changes: 60 additions & 0 deletions transforms/remove-unused-imports.ts
Original file line number Diff line number Diff line change
@@ -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;
4 changes: 2 additions & 2 deletions transforms/v2-to-v3.ts
Original file line number Diff line number Diff line change
@@ -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);

Expand All @@ -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;
Expand Down

0 comments on commit c5bdbe1

Please sign in to comment.