[HN Gopher] Show HN: ts-remove-unused - Remove unused code from ...
       ___________________________________________________________________
        
       Show HN: ts-remove-unused - Remove unused code from your TypeScript
       project
        
       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!
        
       Author : kazushisan
       Score  : 129 points
       Date   : 2024-09-16 08:39 UTC (3 days ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | cdaringe wrote:
       | Cool. A prettier plugin does this too. Cant remember which, but
       | maybe https://www.npmjs.com/package/prettier-plugin-organize-
       | impor...?
        
       | devjab wrote:
       | 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 wrote:
         | 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 wrote:
         | 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?
        
           | koolba wrote:
           | > Could you provide examples of such risks?
           | 
           | 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.
        
       | zlies wrote:
       | 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 wrote:
         | 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.
        
           | zlies wrote:
           | You're totally right. I just quickly scanned the readme and
           | had expected a tool that only check the files. But yes, it's
           | a bad idea in general to run random cli programs without
           | checking them in detail
        
             | kazushisan wrote:
             | I'm sorry that it may have been unclear that the tool will
             | make modifications without the check flag. I'm glad you
             | didn't loose any unstated changes :)
             | 
             | I will add a cautionary note to README to make it clear!
        
             | diggan wrote:
             | > had expected a tool that only check the files
             | 
             | 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.
        
             | mewpmewp2 wrote:
             | Don't people do that all the time? I doubt that even you
             | check everything that you run thoroughly. There's a lot of
             | trust we have put into all those tools.
        
           | richardw wrote:
           | I'd maybe recommend inverting the command so it doesn't
           | delete by default but only when you add the relevant flag.
           | Far better to add a little rigmarole than deal with a sad
           | person who wiped their code.
        
             | jamil7 wrote:
             | I'm not the author of the tool, I was just chiming in. But
             | yes, inverting it probably makes sense.
        
               | bmelton wrote:
               | Arguendo, having to type extra characters to enable the
               | functionality that the tool says it will do in its very
               | name seems backwards to me.
        
               | diggan wrote:
               | Besides, the common behavior is to do what it says on the
               | tin by default, but offer flags to make cli's "simulate"
               | their run with "--dry-run" or similar.
               | 
               | I'm not sure I know of any utility that would have the
               | opposite behavior than that.
        
               | cdirkx wrote:
               | The "install -y" pattern is kind of similar. Various
               | tools have as a default that it gives a list of packages
               | that would be installed, and then ask for confirmation.
        
               | senorrib wrote:
               | rm <file> does not ask for any confirmation. Neither do
               | most of the infra tools we use on a daily basis.
        
               | bmelton wrote:
               | `install -y` is a fun analogue to consider here.
               | 
               | 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.
        
         | filleokus wrote:
         | 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?
        
           | umanwizard wrote:
           | It'd be very easy to programmatically check which files in a
           | repo directory are unknown to git. Git status does indeed
           | have a machine-parseable output mode.
        
       | _aqua wrote:
       | Does not work quite well with monorepos, just tried but overall a
       | good idea
        
         | kazushisan wrote:
         | Perhaps you need to specify skip option for the entry point
         | file?
        
       | aiibe wrote:
       | Another good alternative. Archived but still in use at work.
       | https://github.com/nadeesha/ts-prune
        
         | kazushisan wrote:
         | 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
        
       | joseferben wrote:
       | 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 wrote:
         | 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?
        
           | kazushisan wrote:
           | My understanding is that Knip is for detecting unused things
           | and that it's auto-fix feature is still experimental, but
           | with that out of the way...
           | 
           | - 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.
        
             | Cannabat wrote:
             | Thanks for for the detailed answer. I'll try it out some
             | time :)
        
       | worx wrote:
       | 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.
        
       | rickcarlino wrote:
       | 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.
        
         | rty32 wrote:
         | 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".
        
           | rickcarlino wrote:
           | A custom comparative function that returns true or false
           | based on the special requirements of the project might fix
           | that. I don't think the project supports this based on a
           | quick look at the documentation but I would love to see this
           | added as a feature.
        
         | kazushisan wrote:
         | 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...
        
           | rickcarlino wrote:
           | In the JavaScript API, it would be nice if there was the
           | ability to have custom comparative functions for this sort of
           | use case. A user defined function that gets called right
           | before final output which allows the user to create custom
           | filtering rules and return a boolean to indicate inclusion or
           | exclusion. I could see this being useful not only for the
           | case I just presented, but also other cases such as NextJS
           | projects that export default functions in pages that are
           | never imported elsewhere in the project.
        
             | kazushisan wrote:
             | I've been using it for my next.js project and passing
             | something like `/pages\//` to `skip` was good enough for
             | me... Is there something I'm missing out?
        
           | 3523582908 wrote:
           | hey! I was wondering if you'd be willing to explain what
           | project references are and why you would do test config this
           | way?
        
       | conmalkovic wrote:
       | If you are using vs code, it has an extension: find unused
       | exports. It worked so far pretty well in our projects.
       | 
       | https://marketplace.visualstudio.com/items?itemName=iulian-r...
        
       | danfritz wrote:
       | 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.
        
         | thiscatis wrote:
         | Ah the typical "here's a cool thing OP build but I'm using
         | something better-reply"
        
         | guzik wrote:
         | There are also plugins that nicely integrate with ESLint to do
         | this.
        
       | jjice wrote:
       | 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!
        
       | theo-steiner wrote:
       | 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
        
       | istvanmeszaros wrote:
       | 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.
        
       | thestephen wrote:
       | 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.
        
       | bhouston wrote:
       | 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
        
       | dml2135 wrote:
       | 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 wrote:
         | 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.
        
           | dml2135 wrote:
           | Ah got it -- thank you!
        
       | k__ wrote:
       | Pretty cool!
       | 
       | Could this also work with .svelte files, which are essentially
       | html-like files with <script lang="ts">?
        
       | _fat_santa wrote:
       | 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 wrote:
         | 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.
        
       | rarkins wrote:
       | 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.
        
         | fkyoureadthedoc wrote:
         | yeah the `--check` option should be the default mode imo
        
         | kazushisan wrote:
         | 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.
        
       | NathanaelRea wrote:
       | I think the command in the first image in the readme is wrong.
       | Shouldn't it be `npx @line/ts-remove-unused`?
        
       | ditegashi wrote:
       | Deleted 80% of my project. Glad I could revert it but yeah pretty
       | useless at this point
        
       | bschmidt1 wrote:
       | 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 wrote:
       | This is basically malware
        
       ___________________________________________________________________
       (page generated 2024-09-19 23:01 UTC)