Show HN: ts-remove-unused – Remove unused code from your TypeScript project (github.com)
ts-remove-unused is a command line tool for TypeScript projects that auto-fixes unused `export`s. It removes the export keyword from the declaration or the whole declaration based on its usage in the project.
There are some similar tools but they are focused on "detecting" rather than "removing" so I've built one myself. I wanted a solution that's as minimal as possible; config files to specify the files in your project shouldn't be necessary because that info should be already configured in tsconfig.json. All you need to do is to specify your entrypoint file.
Feedback is much appreciated!
67 comments
[ 3.0 ms ] story [ 35.4 ms ] threadUnused imports is perhaps the one area where I would be comfortable removing unused imports. I would never personally allow a third party package into our environment for something like this. I really don’t want to be rude about it, but it’s too “trivial” to rely on an external import for. Not because your code couldn’t be better than ours, it probably will be, but because it’s an unnecessary risk.
For smaller or personal projects I think many people will rely on something like prettier to do the same.
You’re right that this tool may not be useful for some codebases. If your modules are more like “scripts” that include side effects, deleting modules just because it’s not referenced may break things. That should not be the case for react projects that are based on components.
In our development process, we don’t allow the changes made by this tool to be deployed automatically. Instead we make sure the changes are okay by creating a pull request and reviewing. We treat it more like an assistant that does all the cumbersome clean up work in no time.
I only see these potential risks:
1. Using a mix of TS/JS and having some blind spots where we could accidentally delete non-dead code without the compiler noticing.
2. Having and relying on side effects. For example, `export const foobar = thisFnWillDoSthImportant()` and then, yes, removing that would break things.
3. Having separate projects/libs where some consumer might be accessing your exports directly.
Do you see other risks than those?
The risk is adding yet another dependency controlled by a third party that executes on your developer’s local machines and on your CI system.
I will add a cautionary note to README to make it clear!
Isn't the name kind of a giveaway that it does more than just checking? "ts-remove-unused" is plastered all over the place, and you need to even type/paste the name into your terminal before you can use it.
I'm not sure I know of any utility that would have the opposite behavior than that.
A "Hey, this is really going to delete files. If you're just playing around here, maybe try it with the --dry-run flag" seems sane and (so long as it's asked for) means less syntax to have to know up front.
An alternative is to have a check “Are you sure?” Just before delete, with a -fuckit_yolo flag to override the check.
I once had a desktop app and realised over a while that just making copious backups (50, IIRC) meant users were less likely to get angry when they had lots of extra safety, even from their own actions.
> An alternative is to have a check “Are you sure?” Just before delete, with a -fuckit_yolo flag to override the check.
Alternatively, is there any point you'd run this "ts-remove-unused" tool and you don't want it to delete anything? The main point of the tool is to delete things, so not sure what other use cases you have if you don't want it to delete something.
I've never interacted with git programatically so I don't know how messy it would be to implement. But for tools that mostly operate on "whole files" rather than lines in files, I guess it shouldn't be that tricky?
OP, can you describe differences from knip?
- In general, I personally do not like the idea of having to add another config file in my repo. I feel it's contradictory that I need to add more to my codebase to clean up the clutter in my codebase. I understand that relying on tsconfig to specify the target files has its pros and cons but I hope that my tool will encourage users to maintain their tsconfig. - Knip has its own ecosystem around removing unused stuff in your codebase (not limited to exports). I personally prefer tools that are more single-purposed and does one thing right so I don't have any ideas for expanding features; It will be focused on auto-fixing unused code caused by unncessary `export`s.
But other than that it's pretty nice, I might look into the code to see if I can help with that small bug.
I’ve tried tools like this in the past within projects that have high test coverage but I have never had any luck because of this edge case.
If you are providing a library, it's possible you are exporting a function that is meant to be used by downstream code, and that function is isolated from other parts of the code (so never used by other functions but only tests)
If you are writing "product" code, most likely this is just dead code. But there are also edge cases where a function is used as the entry point for other code outside the current repository etc.
Put it this way -- if you are given a codebase you have never seen before, and you see a function only imported by test on the first day. Would you remove it without hesitation? Probably not.
I feel this is likely something that must require human experience to be done "correctly".
https://www.typescriptlang.org/docs/handbook/project-referen...
Some things that project references will solve: - importing test files from your impl files will throw an error - you can specify what types are available. for example you shouldn't be able to use `@types/node` in your frontend code or the `DOM` type in your test code (or vice-versa) - no type checking the whole repository again just because you changed the test.
I think there's quite some control over how strict you can be when you say you're using TypeScript. Project references is another way of making your config more robust, and it may not be necessary depending on your needs (like when some people disable strict:true). I think it's a better choice and relying on a properly configured project shouldn't be a issue for users who are actually going to use this (who are likely maintainers of their project)
https://marketplace.visualstudio.com/items?itemName=iulian-r...
Has more features (like excluding enums) and works very well in large code bases.
For python there is a lib for this, but it is a bit crappy.
One interesting observation: when using it with our Next.js project, it flags all page TypeScript files as unused. This inadvertently highlights a potential drawback of file-system based routing - it can lead to less explicit code relationships.
BTW a complimentary tool I've used in the past is depcheck, it is an npm package that removes unused dependencies from your npm package.json file. Smaller package.json files means faster "npm install" and also smaller docker files.
https://www.npmjs.com/package/depcheck
That’s a genuine question — I’m only passingly familiar with tree-shaking so I may have a misunderstanding of what it does.
Could this also work with .svelte files, which are essentially html-like files with <script lang="ts">?
[1]: https://github.com/nadeesha/ts-prune
[2]: https://github.com/webpro-nl/knip
It deleted 100s of files, most of which were Jest test files, and potentially all of which were a mistake. I restored them all with `git restore $(git ls-files -d)`.
I then ran `tsc` on the remaining _modified_ files and `Found 3920 errors in 511 files.`
Obviously at that point I had no choice but to discard all changes and unfortunately I would not recommend this for others to even try.
BTW the VSCode extension which someone else linked to discovered everything perfectly, which is another hint that your tool needs to improve, not your users.
Even if tsconfig.json was very important, and the users are all wrong and you're right, the only thing your readme says is: "The CLI will respect the tsconfig.json for loading source files." which is insufficient.
Most transformations like this are not possible with pure static analysis and require some domain knowledge (or repo-specific knowledge) in order to pull off correctly. This is because some code gets "used" in ways that are not apparent i the code.
Enjoy!
This looks great, particularly the `skip` and `mode` options (which I'm guessing several commenters here missed).
I suppose it should work just as well with monorepos or any other directory hierarchy right? It only "knows" files are unused because they're never referenced in any code within the defined `projectRoot, and it only knows exports are unused whenever they're never imported?
Cool project, will definitely try it soon.
I've added a more detailed explanation to README so I hope it will change your mind if you've previously had a negative impression :)
https://github.com/line/ts-remove-unused#readme