{"id":13415383,"url":"https://github.com/joshuaclayton/unused","last_synced_at":"2025-10-03T20:31:46.883Z","repository":{"id":56881783,"uuid":"57289216","full_name":"joshuaclayton/unused","owner":"joshuaclayton","description":"Deprecated; see https://github.com/unused-code/unused","archived":true,"fork":false,"pushed_at":"2020-05-28T02:48:16.000Z","size":272,"stargazers_count":854,"open_issues_count":1,"forks_count":24,"subscribers_count":20,"default_branch":"master","last_synced_at":"2024-12-29T23:37:31.271Z","etag":null,"topics":["cli","code-quality","ctags","elixir","haskell","ruby"],"latest_commit_sha":null,"homepage":"https://unused.codes","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joshuaclayton.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-04-28T09:37:11.000Z","updated_at":"2024-12-22T14:59:39.000Z","dependencies_parsed_at":"2022-08-20T23:10:41.178Z","dependency_job_id":null,"html_url":"https://github.com/joshuaclayton/unused","commit_stats":null,"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshuaclayton%2Funused","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshuaclayton%2Funused/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshuaclayton%2Funused/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshuaclayton%2Funused/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joshuaclayton","download_url":"https://codeload.github.com/joshuaclayton/unused/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235184430,"owners_count":18949253,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","code-quality","ctags","elixir","haskell","ruby"],"created_at":"2024-07-30T21:00:47.938Z","updated_at":"2025-10-03T20:31:41.543Z","avatar_url":"https://github.com/joshuaclayton.png","language":"Haskell","funding_links":[],"categories":["Haskell"],"sub_categories":[],"readme":"# Unused [![Build Status](https://travis-ci.org/joshuaclayton/unused.svg?branch=master)](https://travis-ci.org/joshuaclayton/unused)\n\nA command line tool to identify unused code.\n\n# NOTICE: As of May 27, 2020, this version of Unused has been deprecated\n\nUnused has been rewritten and now lives at https://github.com/unused-code/unused\n\nIssues, updates, and all future work will occur there.\n\n![Image of Unused Output](http://i.giphy.com/3o7qDT1I4OfQxnJTvW.gif)\n\n## \"What kinds of projects can I used it on?\"\n\nAnything.\n\nYes, literally anything.\n\nIt's probably best if you have a file generated from `ctags` it can read from\n(it looks in `.git`, `tmp`, and the root directory for a `tags` file), but if\nyou have another way to pipe a bunch of\nmethods/functions/classes/modules/whatever in, that works too.\n\nRight now, there are some special cases built in for Rails and Phoenix apps\n(specifically, assumptions about what's fine to only have one reference to,\ne.g. Controllers in Rails and Views in Phoenix), but it'll work on Rubygems,\nElixir packages, or anything else.\n\nThat said, **be confident the code you're removing won't break your program**.\nEspecially with projects built in Ruby, Elixir, or JavaScript, there are ways\nto dynamically trigger or define behavior that may be surprising. A test suite\ncan help here, but still cannot determine every possible execution path.\n\n## Installing and Updating\n\n### Homebrew (Recommended)\n\nYou can install [my formulae] via [Homebrew] with `brew tap`:\n\n```sh\nbrew tap joshuaclayton/formulae\n```\n\nNext, run:\n\n```sh\nbrew install unused\n```\n\n[my formulae]: https://github.com/joshuaclayton/homebrew-formulae\n[Homebrew]: http://brew.sh/\n\nThis will install `unused` and its corresponding dependencies.\n\nTo update, run:\n\n```sh\nbrew update\nbrew upgrade unused\n```\n\nAlternatively, you can install with [Stack] or by hand. Because it needs to compile, installation times may vary, but it's often several minutes.\n\n### Stack\n\nIf you already have [Stack] installed, ensure you have the latest list of\npackages:\n\n```sh\nstack update\n```\n\nVerify Stack is using at least `lts-6.0` when installing by checking the\nglobal project settings in `~/.stack/global-project/stack.yaml`.\n\nOnce that is complete, run:\n\n```sh\nstack install unused\n```\n\nThis will install unused in the appropriate directory for Stack; you'll want\nto ensure your `$PATH` reflects this.\n\n### Installing by hand\n\nThis project is written in [Haskell] and uses [Stack].\n\nOnce you have these tools installed and the project cloned locally:\n\n```sh\nstack setup\nstack install\n```\n\nThis will generate a binary in `$HOME/.local/bin`; ensure this directory is in\nyour `$PATH`.\n\n[Haskell]: https://www.haskell.org\n[Stack]: http://www.haskellstack.org\n\n### Install via Docker\n\nOnce [Docker is installed], create a binary within your `$PATH` to run the\nimage:\n\n```bash\n#!/usr/bin/env bash\n\ndocker run --rm -it -v $(pwd):/code joshuaclayton/unused unused $@\n```\n\nNote that, because Unused will be running inside of a virtual machine, it will\ntake longer to generate output than were you to install via\npreviously-mentioned methods.\n\n[Docker is installed]: https://docs.docker.com/engine/installation\n\n## Using Unused\n\n`unused` attempts to read from common tags file locations (`.git/tags`,\n`tags`, and `tmp/tags`).\n\nIn an application where the tags file exists, run:\n\n```sh\nunused\n```\n\nIf you don't have a tags file, you can generate one by running:\n```sh\ngit ls-files | xargs ctags\n```\n\nIf you want to specify a custom tags file, or load tokens from somewhere else,\nrun:\n\n```sh\ncat .custom/tags | unused --stdin\n```\n\nTo view more usage options, run:\n\n```sh\nunused --help\n```\n\n## Troubleshooting\n\n### Ctags (and a corresponding workflow) isn't configured\n\n[Exuberant Ctags] \\(or another tool that will generate a tags file, like\n[hasktags] for Haskell projects) is required to use `unused` correctly;\nhowever, the version of `ctags` that ships with OS X (`/usr/bin/ctags`) is an\nolder version won't work with many languages (that BSD version of `ctags` says\nit \"makes a tags file for ex(1) from the specified C, Pascal, Fortran, YACC,\nlex, and lisp sources.\")\n\n[hasktags]: https://hackage.haskell.org/package/hasktags\n\nInstallation via Homebrew includes the `ctags` dependency. You can also run\n`brew install ctags` by hand.  If you're not on OS X, use your favorite package\nmanager and refer to the [Exuberant Ctags] site for download instructions.\n\n[Exuberant Ctags]: http://ctags.sourceforge.net/\n\n#### Ctags manual run\n\nIf you're using `ctags` to generate a `tags` file prior to running `unused` and\ndon't have a workflow around automatically generating a `tags` file, run:\n\n```sh\ngit ls-files | xargs ctags -f tmp/tags\n```\n\nThis will take your `.gitignore` into account and write the tags file to\n`tmp/tags`. Be sure to write this to a location that's ignored by `git`.\n\nWhile this process allows a developer to get started, it requires remembering\nto run this command before running `unused`. Let's explore how to automate this\nprocess.\n\n#### Ctags automatic runs via `git` hooks\n\nWith `ctags` installed, you'll likely want to configure your workflow such that\nyour tags file gets updated periodically without any action on your part. I\nrecommend following the [instructions outlined by Tim Pope] on this matter,\nwhich discusses a workflow coupled to `git` for managing the tags file. It\nincludes shell scripting that may not look \"effortless\"; however, the fact this\nis automated helps to ensure `unused` is running against new versions of the\ncode as you (and other teammates, if you have any) are committing.\n\nAs he suggests, you'll want to run `git init` into the directories you want\nthis hook, and to manually run the hook:\n\n```sh\ngit ctags\n```\n\n`unused` is configured to look for a tags file in three different directories,\nincluding `.git/` as the article suggests, so no further configuration will be\nnecessary with `unused`.\n\n[instructions outlined by Tim Pope]: http://tbaggery.com/2011/08/08/effortless-ctags-with-git.html\n\n### \"Calculating cache fingerprint\" takes a long time\n\n`unused` attempts to be intelligent at understanding if your codebase has\nchanged before running analysis (since it can be time-consuming on large\ncodebases). To do so, it calculates a \"fingerprint\" of the entire directory by\nusing `md5` (or `md5sum`), along with `find` and your `.gitignore` file.\n\nIf you're checking in artifacts (e.g. `node_modules/`, `dist/`, `tmp/`, or\nsimilar), `unused` will likely take significantly longer to calculate the\nfingerprint.\n\nPer the `--help` documentation, you can disable caching with the `-C` flag:\n\n```sh\n$ unused -C\n```\n\n### \"No results found\" when expecting results\n\nIf you're expecting to see results but `unused` doesn't find anything, verify\nthat any artifacts `unused` uses (e.g. the `tags` file, wherever it's located)\nor generates (e.g. in `PROJECT_ROOT/tmp/unused`) is `.gitignore`d.\n\nWhat might be happening is, because unused searches for tokens with `ag`\n(which honors `.gitignore`), it's running into checked-in versions of the\ntokens from other files, resulting in duplicate occurrences that aren't\nrepresentative of the actual codebase. The most obvious might be the `tags`\nfile itself, although if you're using an IDE that runs any sort of analysis\nand that's getting checked in somehow, that may cause it too.\n\nOne final piece to check is the number of tokens in the tags file itself; if\n`ctags` is misconfigured and only a handful of tokens are being analyzed, they\nall may have low removal likelihood and not display in the default results\n(high-likelihood only).\n\n### Analysis takes a long time due to a large number of terms found\n\nIn my experience, projects under 100,000LOC should have at most around 8,000\nunique tokens found. This obviously depends on how you structure your\nclasses/modules/functions, but it'll likely be close.\n\nIf you're seeing more than 15,000 terms matched (I've seen upwards of 70,000),\nthis is very likely due to misconfiguration of `ctags` where it includes some\namount of build artifacts. In Ruby, this might be a `RAILS_ROOT/vendor`\ndirectory, or if you're using NPM, `APP_ROOT/node_modules` or\n`APP_ROOT/bower_components`.\n\nWhen configuring `ctags`, be sure to include your `--exclude` directives; you\ncan [find an example here].\n\n[find an example here]: https://github.com/joshuaclayton/dotfiles/commit/edf35f2a3ca2204a7c6796c3685b7da34bddf5fb#diff-6d7e423e99befb791a7db6ae51126747R76\n\n## Custom Configuration\n\nThe first time you use `unused`, you might see a handful of false positives.\n`unused` will look in two additional locations in an attempt to load\nadditional custom configuration to help improve this.\n\n### Configuration format\n\n```yaml\n# Language or framework name\n#   e.g. Rails, Ruby, Go, Play\n- name: Framework or language\n  # Collection of matches allowed to have one occurrence\n  autoLowLikelihood:\n    # Low likelihood match name\n    - name: ActiveModel::Serializer\n      # Flag to capture only capitalized names\n      #   e.g. would match `ApplicationController`, not `with_comments`\n      classOrModule: true\n\n      # Matcher for `.*Serializer$`\n      #   e.g. `UserSerializer`, `ProjectSerializer`\n      termEndsWith: Serializer\n\n      # Matcher for `^with_.*`\n      #   e.g. `with_comments`, `with_previous_payments`\n      termStartsWith: with_\n\n      # Matcher for `^ApplicationController$`\n      termEquals: ApplicationController\n\n      # Matcher for `.*_factory.ex`\n      #   e.g. `lib/appname/user_factory.ex`, `lib/appname/project_factory.ex`\n      pathEndsWith: _factory.ex\n\n      # Matcher for `^app/policies.*`\n      #   e.g. `app/policies/user_policy.rb`, `app/policies/project_policy.rb`\n      pathStartsWith: app/policies\n\n      # list of termEquals\n      # Matcher allowing any exact match from a list\n      allowedTerms:\n      - index?\n      - edit?\n      - create?\n```\n\n### `~/.unused.yml`\n\nThe first location is `~/.unused.yml`. This should hold widely-used\nconfiguration roughly applicable across projects. Here's an example of what\nmight be present:\n\n```yaml\n- name: Rails\n  autoLowLikelihood:\n    - name: ActiveModel::Serializer\n      termEndsWith: Serializer\n      classOrModule: true\n    - name: Pundit\n      termEndsWith: Policy\n      classOrModule: true\n      pathEndsWith: .rb\n    - name: Pundit Helpers\n      allowedTerms:\n        - Scope\n        - index?\n        - new?\n        - create?\n        - show?\n        - edit?\n        - destroy?\n        - resolve\n    - name: JSONAPI::Resources\n      termEndsWith: Resource\n      classOrModule: true\n      pathStartsWith: app/resources\n    - name: JSONAPI::Resources Helpers\n      allowedTerms:\n      - updatable_fields\n      pathStartsWith: app/resources\n```\n\nI tend to work on different APIs, and the two libraries I most commonly use\nhave a fairly similar pattern when it comes to class naming. They both also\nuse that naming structure to identify serializers automatically, meaning they\nvery well may only be referenced once in the entire application (when they're\ninitially defined).\n\nSimilarly, with Pundit, an authorization library, naming conventions often\nmean only one reference to the class name.\n\nThis is a file that might grow, but is focused on widely-used patterns across\ncodebases. You might even want to check it into your dotfiles.\n\n### `APP_ROOT/.unused.yml`\n\nThe second location is `APP_ROOT/.unused.yml`. This is where any\nproject-specific settings might live. If you're working on a library before\nextracting to a gem or package, you might have this configuration take that\ninto account.\n\n### Validation\n\n`unused` will attempt to parse both of these files, if it finds them. If\neither is invalid either due to missing or mistyped keys, an error will be\ndisplayed.\n\n## Requirements\n\nUnused leverages [Ag](https://github.com/ggreer/the_silver_searcher) to\nanalyze the codebase; as such, you'll need to have `ag` available in your\n`$PATH`. This is set as an explicit dependency in Homebrew.\n\nAlternatively, if you'd like to use\n[RipGrep](https://github.com/BurntSushi/ripgrep), you can do so with the\n`--search rg` flag. Be sure to have RipGrep installed first.\n\n## Testing\n\nTo run the test suite, run:\n\n```sh\nstack test\n```\n\n## License\n\nCopyright 2016-2018 Josh Clayton. See the [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshuaclayton%2Funused","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoshuaclayton%2Funused","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshuaclayton%2Funused/lists"}