├── .babelrc ├── .eslintrc ├── .gitignore ├── README.md ├── components_test ├── componentImported.jsx ├── componentUnimported.jsx └── mainComponent.jsx ├── docs ├── demo.gif └── logo.png ├── package-lock.json ├── package.json ├── scripts └── build.sh ├── src ├── definitions.d.ts ├── index.ts ├── types.ts └── utils.ts └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "node": 6 8 | } 9 | } 10 | ], 11 | "stage-2" 12 | ], 13 | "sourceMaps": "inline" 14 | } 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": ["airbnb-base"], 4 | "globals": { 5 | "jest": false, 6 | "expect": false, 7 | "describe": false, 8 | "xdescribe": false, 9 | "it": false, 10 | "xit": false, 11 | "beforeAll": false, 12 | "beforeEach": false, 13 | "afterAll": false, 14 | "afterEach": false 15 | }, 16 | "rules": { 17 | "comma-dangle": [2, "always-multiline"], 18 | "no-restricted-syntax": 0, 19 | "no-await-in-loop": 0 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | 4 | 5 | # Created by https://www.gitignore.io/api/node,macos,windows 6 | 7 | ### macOS ### 8 | *.DS_Store 9 | .AppleDouble 10 | .LSOverride 11 | 12 | # Icon must end with two \r 13 | Icon 14 | 15 | # Thumbnails 16 | ._* 17 | 18 | # Files that might appear in the root of a volume 19 | .DocumentRevisions-V100 20 | .fseventsd 21 | .Spotlight-V100 22 | .TemporaryItems 23 | .Trashes 24 | .VolumeIcon.icns 25 | .com.apple.timemachine.donotpresent 26 | 27 | # Directories potentially created on remote AFP share 28 | .AppleDB 29 | .AppleDesktop 30 | Network Trash Folder 31 | Temporary Items 32 | .apdisk 33 | 34 | ### Node ### 35 | # Logs 36 | logs 37 | *.log 38 | npm-debug.log* 39 | yarn-debug.log* 40 | yarn-error.log* 41 | 42 | # Runtime data 43 | pids 44 | *.pid 45 | *.seed 46 | *.pid.lock 47 | 48 | # Directory for instrumented libs generated by jscoverage/JSCover 49 | lib-cov 50 | 51 | # Coverage directory used by tools like istanbul 52 | coverage 53 | 54 | # nyc test coverage 55 | .nyc_output 56 | 57 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 58 | .grunt 59 | 60 | # Bower dependency directory (https://bower.io/) 61 | bower_components 62 | 63 | # node-waf configuration 64 | .lock-wscript 65 | 66 | # Compiled binary addons (http://nodejs.org/api/addons.html) 67 | build/Release 68 | 69 | # Dependency directories 70 | node_modules/ 71 | jspm_packages/ 72 | 73 | # Typescript v1 declaration files 74 | typings/ 75 | 76 | # Optional npm cache directory 77 | .npm 78 | 79 | # Optional eslint cache 80 | .eslintcache 81 | 82 | # Optional REPL history 83 | .node_repl_history 84 | 85 | # Output of 'npm pack' 86 | *.tgz 87 | 88 | # Yarn Integrity file 89 | .yarn-integrity 90 | 91 | # dotenv environment variables file 92 | .env 93 | 94 | 95 | ### Windows ### 96 | # Windows thumbnail cache files 97 | Thumbs.db 98 | ehthumbs.db 99 | ehthumbs_vista.db 100 | 101 | # Folder config file 102 | Desktop.ini 103 | 104 | # Recycle Bin used on file shares 105 | $RECYCLE.BIN/ 106 | 107 | # Windows Installer files 108 | *.cab 109 | *.msi 110 | *.msm 111 | *.msp 112 | 113 | # Windows shortcuts 114 | *.lnk 115 | 116 | # End of https://www.gitignore.io/api/node,macos,windows 117 | 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | delete-react-zombies 3 |

4 | 5 | [![npm version](https://badge.fury.io/js/delete-react-zombies.svg)](https://badge.fury.io/js/delete-react-zombies) 6 | [![npm](https://img.shields.io/npm/dw/delete-react-zombies.svg)](https://www.npmjs.com/package/delete-react-zombies) 7 | 8 | **CLI to search and delete unimported 🧟 components in your react files** 9 | 10 |

11 | delete-react-zombies 12 |

13 | 14 | ## Install 15 | 16 | ```sh 17 | $ npm install delete-react-zombies 18 | ``` 19 | 20 | ## Usage 21 | 22 | ```sh 23 | $ delete-react-zombies 24 | ``` 25 | 26 | **Options**: 27 | 28 | - `--path` define the path where search zombies (default=process.cwd) 29 | - `--verbose` show in the console the file content to be deleted 30 | - `--force` don't ask confirm before delete files. 31 | 32 | ## How it works 33 | 34 | The package create a list of components in your application. 35 | Based on that, the library search in your files content the keyword `import ${componentName}`, where the `componentName` variable is the name of the component that is exported. 36 | 37 | ## Contributing 38 | 39 | Check the [issue list](https://github.com/CVarisco/delete-react-zombies/issues) to contribute on some activities or to advice new features! 40 | The library is open to everybody, contribute improve your skills. 41 | 42 | `delete-react-zombies` is maintained under [the Semantic Versioning guidelines](http://semver.org/). 43 | 44 | Use `npm run build:ts` while coding. 45 | 46 | ## License 47 | 48 | MIT © [Christian Varisco](https://github.com/CVarisco) 49 | -------------------------------------------------------------------------------- /components_test/componentImported.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | class ComponentUsed extends React.Component { 4 | render() { 5 | return

{this.props.children}

; 6 | } 7 | } 8 | 9 | export default ComponentUsed; 10 | -------------------------------------------------------------------------------- /components_test/componentUnimported.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | class ComponentUnused extends React.Component { 4 | render() { 5 | return

Not used

; 6 | } 7 | } 8 | 9 | export default ComponentUnused; 10 | -------------------------------------------------------------------------------- /components_test/mainComponent.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ComponentUsed from "./componentUsed"; 3 | 4 | class Main extends React.Component { 5 | render() { 6 | return Hello World; 7 | } 8 | } 9 | 10 | export default Main; 11 | -------------------------------------------------------------------------------- /docs/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CVarisco/delete-react-zombies/22aaaab93b77cb1de874ecf710efb13f265904da/docs/demo.gif -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CVarisco/delete-react-zombies/22aaaab93b77cb1de874ecf710efb13f265904da/docs/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delete-react-zombies", 3 | "version": "2.0.1", 4 | "description": "Search and delete unimported components in your react files", 5 | "author": "Christian Varisco ", 6 | "bin": { 7 | "delete-react-zombies": "./dist/index.js" 8 | }, 9 | "engines": { 10 | "node": ">=6" 11 | }, 12 | "main": "./dist/index.js", 13 | "files": [ 14 | "dist" 15 | ], 16 | "scripts": { 17 | "lint": "eslint src test", 18 | "build": "./scripts/build.sh", 19 | "build:ts": "tsc -w", 20 | "watch": "npm-watch", 21 | "try": "node ./dist/index.js", 22 | "test": "jest", 23 | "test:watch": "jest --watch" 24 | }, 25 | "watch": { 26 | "lint": "src/**/*.js", 27 | "build": "src/**/*.js" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/CVarisco/delete-react-zombies.git" 32 | }, 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/CVarisco/delete-react-zombies/issues" 36 | }, 37 | "homepage": "https://github.com/CVarisco/delete-react-zombies#readme", 38 | "devDependencies": { 39 | "@types/inquirer": "0.0.41", 40 | "@types/node": "12.0.4", 41 | "@types/ora": "3.2.0", 42 | "@types/yargs": "11.0.0", 43 | "babel-cli": "6.26.0", 44 | "babel-eslint": "7.1.1", 45 | "babel-preset-env": "1.7.0", 46 | "babel-preset-stage-2": "6.24.1", 47 | "eslint": "4.18.2", 48 | "eslint-config-airbnb-base": "11.0.1", 49 | "eslint-plugin-import": "2.2.0", 50 | "jest": "24.8.0", 51 | "mock-fs": "3.12.1", 52 | "npm-watch": "0.1.7" 53 | }, 54 | "dependencies": { 55 | "inquirer": "6.3.1", 56 | "ora": "3.4.0", 57 | "react-docgen": "4.1.1", 58 | "yargs": "13.2.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | rm -rf dist 2 | tsc -------------------------------------------------------------------------------- /src/definitions.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-docgen'; -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as fs from 'fs'; 3 | import ora from 'ora'; 4 | import { argv as args } from 'yargs'; 5 | import { prompt } from 'inquirer'; 6 | import { isImported, deleteComponents, deleteComponent, isAvailableFile } from './utils'; 7 | const reactDocs = require('react-docgen'); 8 | 9 | /** 10 | * @description Parse with react-docgen and return componentName if exists 11 | */ 12 | function getComponentName(fileContent: string): string { 13 | try { 14 | const parsed = reactDocs.parse(fileContent); 15 | 16 | return parsed.displayName || ''; 17 | } catch (error) { 18 | return ''; 19 | } 20 | } 21 | 22 | /** 23 | * @description Loop into the dir and find React Components. 24 | * The research occurs based on file extension. 25 | * Get the content of the file if the extension is correct, build the component with https://github.com/reactjs/react-docgen to get the componentName 26 | */ 27 | function getComponentsFromDir(path: string): Component[] { 28 | const files = fs.readdirSync(path); 29 | 30 | return files.reduce((acc: Component[], fileName: string): Component[] => { 31 | const filePath = `${path}/${fileName}`; 32 | const isDirectory = fs.statSync(filePath).isDirectory(); 33 | 34 | if (isDirectory) { 35 | return [...acc, ...getComponentsFromDir(filePath)]; 36 | } 37 | 38 | if (isAvailableFile(fileName)) { 39 | const fileContent = fs.readFileSync(filePath, { 40 | encoding: 'utf-8', 41 | }); 42 | const componentName = getComponentName(fileContent); 43 | 44 | if (!componentName) { 45 | return acc; 46 | } 47 | 48 | return [ 49 | ...acc, 50 | { 51 | name: componentName, 52 | path: filePath, 53 | content: fileContent, 54 | }, 55 | ]; 56 | } 57 | 58 | return acc; 59 | }, []); 60 | } 61 | 62 | /** 63 | * @description Loop throuth the list of files and verify if the component name is imported into the file 64 | * If yes, stop the loop 65 | * If no, return the component information to be deleted 66 | */ 67 | function verifyImport(component: Component, listOfComponents: Component[]): Component | undefined { 68 | for (const file of listOfComponents) { 69 | if (isImported(component.name, file.content)) { 70 | return; 71 | } 72 | } 73 | 74 | return component; 75 | } 76 | 77 | /** 78 | * @description For each component verify if is imported on the list of files and create a list of unused components 79 | */ 80 | function getUnusedComponents(components: Component[]): Component[] { 81 | return components.reduce((acc: Component[], curr: Component): Component[] => { 82 | const componentUnUsed = verifyImport(curr, components); 83 | 84 | if (!componentUnUsed) { 85 | return acc; 86 | } 87 | 88 | return [...acc, componentUnUsed]; 89 | }, []); 90 | } 91 | 92 | /** 93 | * @description Loop all components to delete and ask with a question a confirmation 94 | */ 95 | async function confirmDelete(components: Component[]) { 96 | for (const component of components) { 97 | if (args.verbose) { 98 | console.log(component.content); 99 | } 100 | const answer = await prompt([ 101 | { 102 | type: 'confirm', 103 | name: 'confirm', 104 | message: `Do you want to delete ${component.path} ?`, 105 | }, 106 | ]); 107 | //@ts-ignore 108 | if (answer.confirm) { 109 | deleteComponent(component); 110 | } 111 | } 112 | } 113 | 114 | (async function () { 115 | const spinner = ora({ 116 | text: 'Searching zombie components', 117 | }).start(); 118 | 119 | const path = args.path || process.cwd(); 120 | const components = getComponentsFromDir(path); 121 | 122 | console.log(`\n\n${components.length} components found! \n`); 123 | const zombieComponents = getUnusedComponents(components); 124 | 125 | spinner.stop(); 126 | console.log(`${zombieComponents.length} unused components found! \n`); 127 | 128 | if (args.force) { 129 | return deleteComponents(zombieComponents); 130 | } 131 | 132 | await confirmDelete(zombieComponents); 133 | return console.log('\nBye bye!'); 134 | }()); 135 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | interface Component { 2 | name: string; 3 | path: string; 4 | content: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | 3 | export const isImported = (text: string, content: string): boolean => 4 | content.indexOf(`import ${text}`) !== -1; 5 | 6 | export const regexp: RegExp = new RegExp('export default (.*)'); 7 | 8 | export const deleteComponents = (components: Component[]) => components.forEach(deleteComponent); 9 | 10 | export const deleteComponent = (component: Component) => 11 | fs.existsSync(component.path) && fs.unlinkSync(component.path); 12 | 13 | export const isDirectory = (path: string): boolean => fs.statSync(path).isDirectory(); 14 | 15 | export const isAvailableFile = (file: string): boolean => file.search(/.js|.ts|.jsx|.tsx/g) !== -1; 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "umd", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "strict": true, 8 | "alwaysStrict": true, 9 | "outDir": "dist" 10 | }, 11 | "include": ["**/*.ts"], 12 | "exclude": ["node_modules"] 13 | } 14 | --------------------------------------------------------------------------------