· 6 years ago · Aug 28, 2019, 09:11 PM
1export default function transformer(file, api, options) {
2 const j = api.jscodeshift;
3 const root = j(file.source);
4
5 const removeIfUnused = (importSpecifier, importDeclaration) => {
6 const varName = importSpecifier.value.local.name;
7 if (varName === "React") {
8 return false;
9 }
10
11
12 const isUsedInScopes = () => {
13 return j(importDeclaration)
14 .closestScope()
15 .find(j.Identifier, { name: varName })
16 .filter((p) => {
17 if (p.value.start === importSpecifier.value.local.start) return false;
18 if ((p.parentPath.value.type === "Property" && p.name === "key")) return false;
19 if (p.name === "property") return false;
20 return true;
21 })
22 .size() > 0;
23 };
24
25 // Caveat, this doesn't work with annonymously exported class declarations.
26 const isUsedInDecorators = () => { // one could probably cache these, but I'm lazy.
27 let used = false;
28 root.find(j.ClassDeclaration)
29 .forEach((klass) => {
30 used = used || (klass.node.decorators && j(klass.node.decorators)
31 .find(j.Identifier, { name: varName })
32 .filter((p) => {
33 if ((p.parentPath.value.type === "Property" && p.name === "key")) return false;
34 if (p.name === "property") return false;
35 return true;
36 })
37 .size() > 0);
38 });
39 return used;
40 };
41
42 if (!(isUsedInScopes() || isUsedInDecorators())) {
43 j(importSpecifier).remove();
44 return true;
45 }
46 return false;
47 };
48
49 const removeUnusedDefaultImport = (importDeclaration) => {
50 return j(importDeclaration).find(j.ImportDefaultSpecifier).filter((s) => removeIfUnused(s, importDeclaration)).size() > 0;
51 };
52
53 const removeUnusedNonDefaultImports = (importDeclaration) => {
54 return j(importDeclaration).find(j.ImportSpecifier).filter((s) => removeIfUnused(s, importDeclaration)).size() > 0;
55 };
56
57
58 // Return True if somethin was transformed.
59 const processImportDeclaration = (importDeclaration) => {
60 // e.g. import 'styles.css'; // please Don't Touch these imports!
61 if (importDeclaration.value.specifiers.length === 0) return false;
62
63 const hadUnusedDefaultImport = removeUnusedDefaultImport(importDeclaration);
64 const hadUnusedNonDefaultImports = removeUnusedNonDefaultImports(importDeclaration);
65
66 if (importDeclaration.value.specifiers.length === 0) {
67 j(importDeclaration).remove();
68 return true;
69 }
70 return hadUnusedDefaultImport || hadUnusedNonDefaultImports;
71 };
72
73 return root.find(j.ImportDeclaration)
74 .filter(processImportDeclaration)
75 .size() > 0 ? root.toSource(options.printOptions || { quote: "single" }) : null;
76}