jollygoodcode.github.io
jollygoodcode.github.io copied to clipboard
Find Unused Code
We will use a CLI tool to find unused code automatically 🔎🔎🔎.
Unused
Unused is a CLI tool to find unused code, written in 100% Haskell by awesome Josh Clayton.
Install Unused on macOS
$ brew tap joshuaclayton/formulae
$ brew install unused
Ctags for macOS
Install Ctags via homebrew instead of using system built-in older Ctags:
$ brew install ctags
If you having problem that your Ctags does not load homebrew-installed one, add an alias (zsh):
alias ctags="`brew --prefix`/bin/ctags"
Generate index file
For Ruby files here is an example:
$ ctags -f .git/tags -R $(git ls-files | grep .rb)
A .tags
file under .git
folder will be generated, which contains the index of your Ruby code. Don't forget to gitignore your tags file.
Find Unused Code
$ unused -s -g none
* -s,--single-occurrence Display only single occurrences
* -g,--group-by ARG [Allowed: directory, term, file, none] Group results
unused will do the hard work, find where unused code are, and report to you:
BulkCustomerDashboard 1, 1 app/dashboards/bulk_customer_dashboard.rb used once
UserSerializer 1, 1 app/serializers/user_serializer.rb used once
authorized_repos 1, 1 lib/github_api.rb used once
create_subscription_record 1, 1 app/services/repo_subscriber.rb used once
excluded_file? 1, 1 lib/ext/scss-lint/config.rb used once
has_something? 1, 1 spec/models/linter/ruby_spec.rb used once
register_email 1, 1 spec/models/linter/ruby_spec.rb used once
stub_commit 1, 1 spec/services/build_runner_spec.rb used once
stub_customer_with_discount_find_request 1, 1 spec/support/helpers/stripe_api_helper.rb used once
stubbed_style_checker_with_config_file 1, 1 spec/services/build_runner_spec.rb used once
token= 1, 1 app/models/user.rb used once
user_names 1, 1 spec/models/linter/ruby_spec.rb used once
verified_request? 1, 1 app/controllers/application_controller.rb used once
Wow, unused code found, delete them and then sent a Pull Request!
Happy Housekeeping! 🏡
Note that Unused is meant to guide, though, not give definitive answers to what can be removed. -- Josh Clayton @joshuaclayton
Thanks for this HowTo! One remark, for me the definition of alias for ctags was not necessary, since brew automatically linked it correctly.
@JuanitoFatas nice post! A couple things:
- if the ctags file exists at
.git/tags
,tags
, ortmp/tags
within the repo,unused
will load it automatically, meaning you won't have to pipe it in, so all that'd be needed is to rununused
-
-s
will filter so only single-occurrence tokens are displayed, allowing you to omit piping togrep
-
-g none
will also remove the grouping, so if you want individual lines without headings, you can disable those as well (a side-effect of usinggrep
)
@joshuaclayton for me using tags
file doesn't work. I use ctags -f tags -R $(git ls-files | grep .rb)
and then call unused
and it does not find anything. Weird thing is, if I use the cat $file | unused --stdin
way, it works fine for a tags file starting with a .
, but for files without I just get "Unused found no results". If I move the file outside of my project directory and cat it from there, it also works.
Any clue what's going on there? Running on OS X 10.11.5. btw.
@Aqualon interesting; if you cd into the project directory (where you're running unused
) and run cat tags | wc -l
, what is returned?
When unused
can't find a tags file, it'll tell you (screenshot below). If it's saying there aren't results, it's often that the file exists but doesn't contain much/anything (often due to misconfiguration from ctags).

Let me know if anything looks off in the generated tags
file - that'd be my first guess.
@joshuaclayton it finds 1823 lines and for both cases (the .tags
file piped into unused and just having the same file as tags
) I get the output Unused: analyzing 919 terms.
I think I have an idea what could be going on. Can it be that unused scans all files in the project directory again and without tags
file in .gitignore
it scans it too and thus gets confused? After I added it to .gitignore
unused works. And that's why it maybe also works, if the file is outside the project directory and not inside.
@Aqualon yes, that'd probably do it; a tags file is (as far as I've ever known) always something you'd want git to ignore, since each developer may have different ways of generating tags on their machine.
It might be good for unused to count the tokens, and if it's non-zero, issue a message if nothing comes up - thoughts?
For projects with literally no unused code, it might be confusing - but for all others, it might be helpful to provide insight as to why nothing's showing up.
@JuanitoFatas @joshuaclayton what would be the alias option for a fish shell?
@askl56 if your $PATH
is configured correctly (e.g. /usr/local/bin
has higher priority than /usr/bin
) you shouldn't have to write an alias at all.
That said, https://fishshell.com/docs/current/commands.html#alias looks like a good place to start (I haven't used fish myself)
Nah I know about aliases, the problem is that the ` syntax doesn't work in fish (within the quotes)
@askl56 ah - I'd check $PATH
first, packages installed with Homebrew should take precedence over any system-installed binaries.
@JuanitoFatas the alias
line seems confusing and totally unnecessary when $PATH
is configured correctly; thoughts on removing that bit of the post too?
So @joshuaclayton what would be the command without the alias?
@askl56 The alias is necessary if your $PATH
is misconfigured. which ctags
should return the Homebrew-installed version. If it does, nothing else is required and you can ignore the alias section entirely. If it doesn't, follow Homebrew's instructions to configure your $PATH
correctly.
@JuanitoFatas nice post! A couple things:
if the ctags file exists at .git/tags, tags, or tmp/tags within the repo, unused will load it automatically, meaning you won't have to pipe it in, so all that'd be needed is to run unused -s will filter so only single-occurrence tokens are displayed, allowing you to omit piping to grep -g none will also remove the grouping, so if you want individual lines without headings, you can disable those as well (a side-effect of using grep)
Thanks for the tips, I edited the post, much appreciated!
The alias is necessary if your $PATH is misconfigured. which ctags should return the Homebrew-installed version. If it does, nothing else is required and you can ignore the alias section entirely. If it doesn't, follow Homebrew's instructions to configure your $PATH correctly.
@joshuaclayton After fixed my $PATH
, I don't need the alias, too. Thanks! 👍
@joshuaclayton maybe a hint in the readme which files are ignored by default (e.g. to me it looks like all dot-files are not scanned) and that the tags file should be in .gitignore? Programmatically you could check for the default run, if the found tags file is not ignored by git and thus could lead to not finding things? (I'm quite new to the whole ctags thing, so don't know what others would expect).
@JuanitoFatas good post! I'm currently trying this out on a number of ruby codebases and ran into this Github issue/post. I realize that this might be a dumb question, but just for curiosity's sake: what do the 2 number columns mean (e.g. 1,1
)? They sometimes differ but at least one of them refers to the number of occurrences of the class/method, but I don't know what the other one is for.
@joshuaclayton nice tool btw!
this might be a dumb question
This is a good question! 😊
what do the 2 number columns mean (e.g. 1,1)?
The first 1
is how many times it occurs, second 1
is how many matches.
For example, in my project, I ran unused
, I had this one from output:
fetch! 2, 3 spec/models/rubygem_spec.rb only the definition and corresponding tests exist
Then I do a search fetch!
in my editor (against .rb
files only, since I only created ctags for .rb
files), the result is:
3 matches across 2 files.
Hope this helps.
@JuanitoFatas @padi spot on!
I display the numbers because there are times where there are multiple occurrences but it's still flagged as high-likelihood for removal (e.g. an instance method where the only use looks to be in the unit tests).
@padi I'd love to hear any thoughts on how to make this more clear; interested in opening an issue?
So, does the occurring mean in how many files does the tag/method/class occur, regardless of how many times it appeared on the same file?
@JuanitoFatas now that I remember, long time no see from rubyconf.ph. @joshuaclayton thanks. I made an issue as a point of discussion. https://github.com/joshuaclayton/unused/issues/60