top | item 41554014

Show HN: ts-remove-unused – Remove unused code from your TypeScript project

141 points| kazushisan | 1 year ago |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!

66 comments

order

rarkins|1 year ago

I tried it on https://github.com/renovatebot/renovate

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.

kazushisan|1 year ago

You need a valid tsconfig that defines the scope of the project and it seems renovate’s tsconfig doesn’t meet this requirement. You can always --skip manually as an alternative option.

_jayhack_|1 year ago

This is simple and configurable with Codegen - see the results on `renovate` here: https://www.codegen.sh/codemod/4553/public/diff

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!

fkyoureadthedoc|1 year ago

yeah the `--check` option should be the default mode imo

zlies|1 year ago

You should switch the default to not delete any files and modify/remove the files only with some flag (--dry-run=false, --rm, --delete, etc). I just deleted all files accidentally in a monorepo :D Luckily I didn't had any uncommitted changes and could recover using git

jamil7|1 year ago

It's maybe reasonable to change the defaults, but I think you should also be mindful of running any random cli program in your codebase, the top of the Github readme does indicate that the --check mode runs it without deleting files.

filleokus|1 year ago

It would be kinda cool to use git status to avoid accidental data loss for tools like this.

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?

worx|1 year ago

I tried it and it's pretty cool, I might introduce it in our company project. I did notice one problem/caveat: It doesn't play nice with dynamic imports. Our project has a few files being lazily imported and the tool seems to think that those files are unused, which is not true, they're just imported with the `import('./file')` syntax.

But other than that it's pretty nice, I might look into the code to see if I can help with that small bug.

_fat_santa|1 year ago

I've been using ts-prune[1] for years at this point. The project is in maintenance mode but works fine so I've kept using it. I've been looking into Knip[2] which is recommended by the authors of ts-prune though it's been slow mostly because there's little incentive with the current tool working fine.

[1]: https://github.com/nadeesha/ts-prune

[2]: https://github.com/webpro-nl/knip

bikitan|1 year ago

I was in a similar spot, but knip offers more information that ts-prune, and ts-prune may trip on some newer TS syntax. I've been happy using knip so far.

ditegashi|1 year ago

Deleted 80% of my project. Glad I could revert it but yeah pretty useless at this point

joseferben|1 year ago

really nice that you're tool focuses on removing. i've been using https://knip.dev/ for detection in monorepos, but it's cumbersome to remove manually.

Cannabat|1 year ago

Knip does have some auto fix capabilities but it’s not perfect yet. The detection is great, though. I use it with `dpdm` to keep things tidy. With these (plus eslint, prettier and typescript) in CI, I feel all warm and fuzzy.

OP, can you describe differences from knip?

rickcarlino|1 year ago

Will it remove exports that are only imported for the sake of testing? Eg: it is only imported by files ending in .test.ts or with __test__ as a parent directory?

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.

kazushisan|1 year ago

I may be opinionated but I believe that the best practice is to configure a separate tsconfig for test files with project references. As long as the test files are not included in the tsconfig passed to ts-remove-unused, it should remove exports that are only used in test files.

https://www.typescriptlang.org/docs/handbook/project-referen...

rty32|1 year ago

I think this can get very nuanced --

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".

devjab|1 year ago

This is cool, as a place which uses Typescript for a lot of things, including back-end services we handle it differently. Basically we have a rather strict linting process setup which will warn developers of unused code in development environments and outright refuse it for staging deployment and forward. I’m not sure we would “dare” to automatically remove it, because for the most part there is a reason it is there. Maybe it’s something that needs to be deleted, but it’s almost always something which needs to be handled in some way.

Unused 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.

kazushisan|1 year ago

Thanks for the input! I think there may be a misunderstanding about what this does. Existing linters work great for detecting unused code within a file but once you add `export` to it, you can’t detect unused code with linters even if it’s not referenced from any file.

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.

worx|1 year ago

Could you provide examples of such risks? Because in my understanding, if some function/constant is exported but never imported anywhere, then it must be dead code and never run. And if it was reachable, then TypeScript would fail the compilation. As such, it sounds reasonably safe to me to remove it.

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?

thestephen|1 year ago

Great tool! It uncovered a surprising amount of unnecessary exports in our codebase. Really streamlines things.

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.

kazushisan|1 year ago

I'm not trying to be offensive here, but I've realized that the design choices of the tool and the explanation in README was a little too difficult and unfriendly for the average TypeScript user.

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

bhouston|1 year ago

Neat! I will add this to my toolbelt.

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

danfritz|1 year ago

I've always used https://github.com/pzavolinsky/ts-unused-exports

Has more features (like excluding enums) and works very well in large code bases.

guzik|1 year ago

There are also plugins that nicely integrate with ESLint to do this.

thiscatis|1 year ago

Ah the typical “here’s a cool thing OP build but I’m using something better-reply”

jjice|1 year ago

Been passively wanting something like this for a while now. We have a good few dynamic imports so I'll have to work around that (as per another comment), but this is a much appreciated tool in the belt!

istvanmeszaros|1 year ago

Ohh my, this is something that I would love in my projects... OK currently I don't have any Typescript project, only Python.

For python there is a lib for this, but it is a bit crappy.

theo-steiner|1 year ago

Any tool that helps delete code safely is a win in my book! Even more so if I happen to personally know the author and randomly stumble over their submission on hn

dml2135|1 year ago

Isn’t this what tree-shaking is for?

That’s a genuine question — I’m only passingly familiar with tree-shaking so I may have a misunderstanding of what it does.

cal85|1 year ago

Similar but different. Tree-shaking generally means excluding unused stuff when bundling for production. This actually deletes unused stuff from your source files, i.e. more for code tidiness.

NathanaelRea|1 year ago

I think the command in the first image in the readme is wrong. Shouldn't it be `npx @line/ts-remove-unused`?

kazushisan|1 year ago

Thanks! will work on a fix. my original assumption was that users would `npm i` beforehand.

k__|1 year ago

Pretty cool!

Could this also work with .svelte files, which are essentially html-like files with <script lang="ts">?

kazushisan|1 year ago

possibly... not a priority at this moment but contributions are welcome!

aiibe|1 year ago

Another good alternative. Archived but still in use at work. https://github.com/nadeesha/ts-prune

kazushisan|1 year ago

I was initially using it but decided to write my own tool because I was too lazy to go through the result of ts-prune and delete the code myself :P

_aqua|1 year ago

Does not work quite well with monorepos, just tried but overall a good idea

kazushisan|1 year ago

Perhaps you need to specify skip option for the entry point file?

bschmidt1|1 year ago

I searched far and wide for this a few years ago for a disastrous React codebase I inherited that had a lot of unused components, never found anything.

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.

bowsamic|1 year ago

This is basically malware