├── .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 |
3 |
4 |
5 | [](https://badge.fury.io/js/delete-react-zombies)
6 | [](https://www.npmjs.com/package/delete-react-zombies)
7 |
8 | **CLI to search and delete unimported 🧟 components in your react files**
9 |
10 |
11 |
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 |
--------------------------------------------------------------------------------