2016-05-12 06:24:42 +03:00
# Unused [![Build Status](https://travis-ci.org/joshuaclayton/unused.svg?branch=master)](https://travis-ci.org/joshuaclayton/unused)
2016-04-28 12:37:06 +03:00
2016-05-12 13:37:30 +03:00
A command line tool to identify unused code.
2016-04-28 12:37:06 +03:00
2016-05-27 13:10:50 +03:00
![Image of Unused Output ](http://i.giphy.com/3o7qDT1I4OfQxnJTvW.gif )
2016-05-11 00:24:28 +03:00
2016-05-12 13:37:30 +03:00
## "What kinds of projects can I used it on?"
Anything.
Yes, literally anything.
2016-05-23 12:02:54 +03:00
It's probably best if you have a ctags file it can read from (it looks in
`.git` , `tmp` , and the root directory for a `tags` file), but if you have
another way to pipe a bunch of methods/functions/classes/modules/whatever in,
that works too.
2016-05-12 13:37:30 +03:00
Right now, there are some special cases built in for Rails and Phoenix apps
(specifically, assumptions about what's fine to only have one reference to,
e.g. Controllers in Rails and Views in Phoenix), but it'll work on Rubygems,
Elixir packages, or anything else.
2016-07-02 13:58:46 +03:00
That said, **be confident the code you're removing won't break your program** .
2016-05-12 13:37:30 +03:00
Especially with projects built in Ruby, Elixir, or JavaScript, there are ways
to dynamically trigger or define behavior that may be surprising. A test suite
can help here, but still cannot determine every possible execution path.
2016-06-26 12:13:11 +03:00
## Installing and Updating
2016-05-12 05:29:29 +03:00
2016-05-12 13:40:29 +03:00
### Homebrew (Recommended)
2016-05-12 05:29:29 +03:00
You can install [my formulae] via [Homebrew] with `brew tap` :
```sh
brew tap joshuaclayton/formulae
```
Next, run:
```sh
brew install unused
```
[my formulae]: https://github.com/joshuaclayton/homebrew-formulae
[Homebrew]: http://brew.sh/
2016-06-26 12:13:11 +03:00
To update, run:
```sh
brew update
brew upgrade unused
```
Alternatively, you can install with [Stack] or by hand.
2016-05-12 05:29:29 +03:00
2016-06-24 16:22:06 +03:00
### Stack
If you already have [Stack] installed, ensure you have the latest list of
packages:
```sh
stack update
```
Verify Stack is using at least `lts-6.0` when installing by checking the
global project settings in `~/.stack/global-project/stack.yaml` .
Once that is complete, run:
```sh
stack install unused
```
This will install unused in the appropriate directory for Stack; you'll want
to ensure your `$PATH` reflects this.
2016-05-12 13:40:29 +03:00
### Installing by hand
2016-05-12 05:29:29 +03:00
2016-06-26 12:13:11 +03:00
This project is written in [Haskell] and uses [Stack].
2016-05-12 05:29:29 +03:00
2016-06-26 12:13:11 +03:00
Once you have these tools installed and the project cloned locally:
2016-05-12 05:29:29 +03:00
```sh
stack setup
stack install
```
This will generate a binary in `$HOME/.local/bin` ; ensure this directory is in
your `$PATH` .
[Haskell]: https://www.haskell.org
[Stack]: http://www.haskellstack.org
2016-04-29 12:55:09 +03:00
## Using Unused
2016-05-21 17:20:25 +03:00
`unused` attempts to read from common tags file locations (`.git/tags`,
`tags` , and `tmp/tags` ).
2016-04-29 12:55:09 +03:00
2016-05-21 17:20:25 +03:00
In an application where the tags file exists, run:
2016-04-29 12:55:09 +03:00
```sh
2016-05-21 17:20:25 +03:00
unused
2016-04-29 12:55:09 +03:00
```
2016-06-21 20:08:48 +03:00
If you don't have a tags file, you can generate one by running:
```sh
ctags -R .
```
2016-05-21 17:20:25 +03:00
If you want to specify a custom tags file, or load tokens from somewhere else,
run:
```sh
cat .custom/tags | unused --stdin
```
To view more usage options, run:
```sh
unused --help
```
2016-04-29 12:55:09 +03:00
2016-07-01 13:35:35 +03:00
## Troubleshooting
### "Calculating cache fingerprint" takes a long time
`unused` attempts to be intelligent at understanding if your codebase has
changed before running analysis (since it can be time-consuming on large
codebases). To do so, it calculates a "fingerprint" of the entire directory by
using `md5` (or `md5sum` ), along with `find` and your `.gitignore` file.
If you're checking in artifacts (e.g. `node_modules/` , `dist/` , `tmp/` , or
similar), `unused` will likely take significantly longer to calculate the
fingerprint.
Per the `--help` documentation, you can disable caching with the `-C` flag:
```sh
$ unused -C
```
### "No results found" when expecting results
If you're expecting to see results but `unused` doesn't find anything, verify
that any artifacts `unused` uses (e.g. the `tags` file, wherever it's located)
or generates (e.g. in `PROJECT_ROOT/tmp/unused` ) is `.gitignore` d.
What might be happening is, because unused searches for tokens with `ag`
(which honors `.gitignore` ), it's running into checked-in versions of the
tokens from other files, resulting in duplicate occurrences that aren't
representative of the actual codebase. The most obvious might be the `tags`
file itself, although if you're using an IDE that runs any sort of analysis
and that's getting checked in somehow, that may cause it too.
One final piece to check is the number of tokens in the tags file itself; if
`ctags` is misconfigured and only a handful of tokens are being analyzed, they
all may have low removal likelihood and not display in the default results
(high-likelihood only).
2016-07-06 13:44:44 +03:00
### Analysis takes a long time due to a large number of terms found
In my experience, projects under 100,000LOC should have at most around 8,000
unique tokens found. This obviously depends on how you structure your
classes/modules/functions, but it'll likely be close.
If you're seeing more than 15,000 terms matched (I've seen upwards of 70,000),
this is very likely due to misconfiguration of ctags where it includes some
amount of build artifacts. In Ruby, this might be a `RAILS_ROOT/vendor`
directory, or if you're using NPM, `APP_ROOT/node_modules` or
`APP_ROOT/bower_components` .
When configuring ctags, be sure to include your `--exclude` directives; you
can [find an example here].
[find an example here]: https://github.com/joshuaclayton/dotfiles/commit/edf35f2a3ca2204a7c6796c3685b7da34bddf5fb#diff-6d7e423e99befb791a7db6ae51126747R76
Allow developer-authored configurations
This enables per-user and per-project configs, located in:
* ~/.unused.yml
* APP_ROOT/.unused.yml
Configurations stack upon each other, not replace; unused provides a
very base config, but additional configurations can be defined.
Per-user configs are best used to suit common types of projects at a
generic level. For example, a developer commonly working in Rails
applications might have a config at ~/.unused.yml for patterns like
Policy objects from Pundit, ActiveModel::Serializers, etc.
Per-project config would be less-generic patterns, ones where re-use
isn't likely or applicable.
See unused's global config:
https://github.com/joshuaclayton/unused/blob/master/data/config.yml
The structure is as follows:
- name: Rails
autoLowLikelihood:
- name: Pundit
pathStartsWith: app/policies
pathEndsWith: .rb
termEndsWith: Policy
classOrModule: true
- name: Pundit Helpers
pathStartsWith: app/policies
allowedTerms:
- Scope
- index?
- new?
- create?
- show?
- edit?
- destroy?
- resolve
- name: Other Language
autoLowLikelihood:
- name: Thing
pathEndsWith: .ex
classOrModule: true
Name each item, and include an autoLowLikelihood key with multiple named
matchers. Each matcher can look for various formatting aspects,
including termStartsWith, termEndsWith, pathStartsWith, pathEndsWith,
classOrModule, and allowedTerms.
2016-06-05 14:40:47 +03:00
## Custom Configuration
The first time you use `unused` , you might see a handful of false positives.
`unused` will look in two additional locations in an attempt to load
additional custom configuration to help improve this.
### Configuration format
```yaml
# Language or framework name
# e.g. Rails, Ruby, Go, Play
- name: Framework or language
# Collection of matches allowed to have one occurrence
autoLowLikelihood:
# Low likelihood match name
- name: ActiveModel::Serializer
# Flag to capture only capitalized names
# e.g. would match `ApplicationController` , not `with_comments`
classOrModule: true
# Matcher for `.*Serializer$`
# e.g. `UserSerializer` , `ProjectSerializer`
termEndsWith: Serializer
# Matcher for `^with_.*`
# e.g. `with_comments` , `with_previous_payments`
termStartsWith: with_
# Matcher for `^ApplicationController$`
termEquals: ApplicationController
# Matcher for `.*_factory.ex`
# e.g. `lib/appname/user_factory.ex` , `lib/appname/project_factory.ex`
pathEndsWith: _factory.ex
# Matcher for `^app/policies.*`
# e.g. `app/policies/user_policy.rb` , `app/policies/project_policy.rb`
pathStartsWith: app/policies
# list of termEquals
# Matcher allowing any exact match from a list
allowedTerms:
- index?
- edit?
- create?
```
### `~/.unused.yml`
The first location is `~/.unused.yml` . This should hold widely-used
configuration roughly applicable across projects. Here's an example of what
might be present:
```yaml
- name: Rails
autoLowLikelihood:
- name: ActiveModel::Serializer
termEndsWith: Serializer
classOrModule: true
- name: Pundit
termEndsWith: Policy
classOrModule: true
pathEndsWith: .rb
- name: Pundit Helpers
allowedTerms:
- Scope
- index?
- new?
- create?
- show?
- edit?
- destroy?
- resolve
- name: JSONAPI::Resources
termEndsWith: Resource
classOrModule: true
pathStartsWith: app/resources
- name: JSONAPI::Resources Helpers
allowedTerms:
- updatable_fields
pathStartsWith: app/resources
```
I tend to work on different APIs, and the two libraries I most commonly use
have a fairly similar pattern when it comes to class naming. They both also
use that naming structure to identify serializers automatically, meaning they
very well may only be referenced once in the entire application (when they're
initially defined).
Similarly, with Pundit, an authorization library, naming conventions often
mean only one reference to the class name.
This is a file that might grow, but is focused on widely-used patterns across
codebases. You might even want to check it into your dotfiles.
### `APP_ROOT/.unused.yml`
The second location is `APP_ROOT/.unused.yml` . This is where any
project-specific settings might live. If you're working on a library before
extracting to a gem or package, you might have this configuration take that
into account.
### Validation
`unused` will attempt to parse both of these files, if it finds them. If
either is invalid either due to missing or mistyped keys, an error will be
displayed.
2016-05-11 18:15:18 +03:00
## Requirements
Unused leverages [Ag ](https://github.com/ggreer/the_silver_searcher ) to
analyze the codebase; as such, you'll need to have `ag` available in your
2016-05-12 05:29:29 +03:00
`$PATH` . This is set as an explicit dependency in Homebrew.
2016-04-29 12:55:09 +03:00
2016-05-11 04:03:56 +03:00
## Testing
To run the test suite, run:
```sh
stack test
```
2016-04-28 12:37:06 +03:00
## License
Copyright 2016 Josh Clayton. See the [LICENSE ](LICENSE ).