{"id":15512861,"url":"https://github.com/testdouble/referral","last_synced_at":"2025-10-12T09:31:52.201Z","repository":{"id":46463939,"uuid":"193310600","full_name":"testdouble/referral","owner":"testdouble","description":"🕵️‍♀️ Find, filter, and sort your Ruby code's definitions \u0026 references","archived":false,"fork":false,"pushed_at":"2024-08-01T23:43:32.000Z","size":115,"stargazers_count":347,"open_issues_count":2,"forks_count":6,"subscribers_count":53,"default_branch":"main","last_synced_at":"2025-01-20T23:47:03.348Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","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/testdouble.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-06-23T05:35:49.000Z","updated_at":"2024-09-12T16:19:38.000Z","dependencies_parsed_at":"2022-09-06T05:11:11.607Z","dependency_job_id":null,"html_url":"https://github.com/testdouble/referral","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/testdouble%2Freferral","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/testdouble%2Freferral/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/testdouble%2Freferral/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/testdouble%2Freferral/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/testdouble","download_url":"https://codeload.github.com/testdouble/referral/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":236196622,"owners_count":19110753,"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":[],"created_at":"2024-10-02T09:53:54.197Z","updated_at":"2025-10-12T09:31:46.877Z","avatar_url":"https://github.com/testdouble.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# Referral 🔍\n\nReferral is a CLI to help you undertake complex analyses and refactorings of\nRuby codebases. It finds, filters, and sorts the definitions \u0026 references of\nmost types of Ruby identifiers (e.g. classes, methods, and variables) throughout\nyour code.\n\nThink of `referral` as a toolkit for tracking down references in your code for\nany number of purposes, offering a boatload of command-line options to\nenable you to efficiently accomplish things like:\n\n* Size up a codebase by gathering basic statistics and spotting usage hotspots\n* Build a to-do list to help you manage a large or complex refactor\n* Quickly make a list of every call to a deprecated method, rather than wait for\n  warnings at runtime\n* Get a sense for how many callers would be impacted if you were to delete a method\n* Before renaming a module, verify there aren't already any other modules with\n  the new name\n* Verify that you removed every reference to a deleted class before you merge\n* Identify dead code, like method definitions that aren't invoked anywhere\n* Catch references that haven't been updated since a change that affected them\n  (according to `git-blame`)\n\nBecause Referral is powered by the introspection made possible by Ruby 2.6's\n[RubyVM::AbstractSyntaxTree](https://ruby-doc.org/core-2.6.3/RubyVM/AbstractSyntaxTree.html)\nAPI, it must be run with Ruby 2.6 or later. Nevertheless, it can often analyze\ncode _listings_ designed to run on older Rubies.\n\n## Install\n\nFrom the command line:\n\n```\n$ gem install referral\n```\n\nOr in your `Gemfile`\n\n```ruby\ngem \"referral\", require: false, group: :development\n```\n\n## How to use Referral\n\n### Basic usage\n\nAt its most basic, you can just run `referral` and it'll scan `**/*.rb` from the\ncurrent working directory and print every reference it finds:\n\n```\n$ referral\napp/channels/application_cable/channel.rb:1:0: module  ApplicationCable\napp/channels/application_cable/channel.rb:2:2: class ApplicationCable Channel\napp/channels/application_cable/channel.rb:2:18: constant ApplicationCable::Channel ActionCable::Channel::Base\n# … and then another 2400 lines (which you can easily count with `referral | wc -l`)\n```\n\nBy default, Referral will sort entries by file, line, and column. Default output\nis broken into 4 columns: `location`, `type`, `scope`, and `name`.\n\nIf you'd like to scan a subset of files, you can pass a final argument with file\npaths and directories. For example, if you only wanted to search code in the\ntop-level of `app/lib` you could run `referral app/lib/*.rb`. Or, if you wanted\nto include subdirectories, `referral app/lib`.\n\nEverything above can be custom-tailored to your purposes, so let's work through\nsome example recipes to teach you Referral's various features below. (Or, feel\nfree to skip to the [full list of\noptions](https://github.com/testdouble/referral#options)).\n\n### Recipe: build a refactoring to-do spreadsheet\n\nWhen I'm undergoing a large refactor, I like to start by grepping around for all\nthe obvious definitions and references that might be affected. Suppose I'm going\nto make major changes to my `User` class. I might use Referral's `--exact-name`\nfilter like this:\n\n```\nreferral --exact-name User,user,@user,@current_user\n```\n\n[**Fun fact:** if I'd have wanted to match on partial names, I could have used the looser\n`--name`, or for fully-qualified names (e.g. `API::User`), the stricter\n`--full-name` option.]\n\nNext, I usually find it easiest to work through a large refactor file-by-file,\nbut in certain cases where I'm looking for a specific type of reference, it\nmakes more sense to sort by the fully-qualified scope, which can be done with\n`--sort scope`:\n\n```\nreferral --exact-name User,user,@user,@current_user --sort scope\n```\n\nThe above will sort results by their fully-qualified names (e.g. `A::B#c`),\nrather than their filenames.\n\nOf course, if we want a checklist, the default output could be made a lot nicer\nfor export to a spreadsheet app like [Numbers](https://www.apple.com/numbers/).\nHere's how you might invoke `referral` to save a tab-separated-values (TSV)\nfile:\n\n```\nreferral --exact-name User,user,@user,@current_user --sort scope --print-headers --delimiter \"\\t\" \u003e user_refs.tsv\n```\n\nWhere `--print-headers` prints an initial row of the selected column names, and `--delimiter\n\"\\t\"` separates each field by a tab (making it easier to ingest for a\nspreadsheet app like Excel or Numbers), before being redirected to the file\n`user_refs.tsv`.\n\nNow, to open it in Numbers, I'd run:\n\n```\nopen -a Numbers user_refs.tsv\n```\n\nAnd you'll be greeted by a spreadsheet. And hey, why not throw a checkbox column\non there while you're at it:\n\n\u003cimg width=\"1272\" alt=\"Screen Shot 2019-06-27 at 1 27 42 PM\" src=\"https://user-images.githubusercontent.com/79303/60287234-64560a00-98df-11e9-9fed-46c68fdaac58.png\"\u003e\n\n\u003e It is important to note that Numbers, like earlier versions of Excel, uses an unsigned Integer for row numbering that limits the number of shown rows to ~65,000. On larger codebases, referral may create more references than this. LibreOffice and newer versions of Excel do not have this limitation on viewing.\n\n### Recipe: detect references you forgot to update\n\nWhen working in a large codebase, it can be really tough to figure out if you\nremembered to update every reference to a class or method across thousands of\nfiles, so Referral ships with the ability to get some basic information from\n`git-blame`, like this:\n\n```\nreferral --column file,line,git_sha,git_author,git_commit_at,full_name\n```\n\nBy setting `--column` to a comma-separated array that includes the above,\nReferral will print results that look like these:\n\n```\ntest/lib/splits_furigana_test.rb 56 634edc04 searls@gmail.com 2017-09-04T13:34:09Z SplitsFuriganaTest#test_nasty_edge_cases.assert_equal\ntest/lib/splits_furigana_test.rb 56 634edc04 searls@gmail.com 2017-09-04T13:34:09Z h\ntest/lib/splits_furigana_test.rb 56 634edc04 searls@gmail.com 2017-09-04T13:34:09Z @subject.call\n```\n\n[**Warning:** running `git-blame` on each file is, of course, a bit slow. Running\nthis command on the [KameSame](https://www.kamesame.com/) codebase took 3 seconds of\nwall-time, compared to 0.7 seconds by default.]\n\nAnd it gets better! Since we're already running `blame`, why not sort every line\nby its most and least recent commit time? You can! To list the\nleast-recently-changed references first, add the option `--sort\nleast_recent_commit`:\n\n```\nreferral --sort least_recent_commit --column file,line,git_sha,git_author,git_commit_at,full_name\n```\n\nIn my case, I see that my least-recently-updated Ruby reference is:\n\n```\napp/channels/application_cable/channel.rb 1  searls@gmail.com 2017-08-20T14:59:35Z ApplicationCable\n```\n\nThe inclusion of `git-blame` fields and sorting can be a powerful tool to\nspot-check a large refactor before deciding to merge it in.\n\n### Recipe: search for a regex pattern and print the source\n\nOnce in a while, I'll want to scan line-by-line in a codebase for lines that\nmatch a given pattern, and in those cases, the `--pattern` option and `source`\ncolumn can be a big help.\n\nSuppose I'm trying to size up a codebase by looking for how many methods appear\nto have a lot of arguments. While _definitely imperfect and regex cannot parse\ncontext-free grammars_, I can get a rough gist by searching for any lines that\nhave 4 or more commas on them:\n\n```\nreferral --pattern \"/^([^,]*,){4,}[^,]*$/\" -c location,source\n```\n\nWhich would yield results like this one:\n\n```\napp/lib/card.rb:22:2:   def self.from_everything(id:, lesson_type:, item:, assignment:, meaning:)\n```\n\nNaturally, other programs like `find` could do this just as well, but the added\nability to see \u0026 sort by when these lines were last updated in git might be\ninteresting. Additionally, suppose you only wanted to find method _definitions_\nwith a lot of (apparent) arguments? You could filter the matches down with\n`--type instance_method,class_method`, too, like this:\n\n```\nreferral --pattern \"/^([^,]*,){4,}[^,]*$/\" -c location,git_commit_at,source -s most_recent_commit --type instance_method,class_method\n```\n\nIn my results, I learned that as recently as June 6th, I wrote a very\nlong method definition:\n\n```\napp/lib/presents_review_result.rb:60:2: 2019-06-02T02:38:01Z   def item_result(study_card_identifier, user, answer, item, learning, judgment, reward)\n```\n\n`find` couldn't have told me that (I don't think)!\n\n### Recipe:  Find calls that have more than 1 argument\n\n[Recently I was upgrading the i18n gem](http://blog.testdouble.com/posts/2019-10-15-lets-hash-this-out/) and came across some bugs introduced by [this change](https://github.com/ruby-i18n/i18n/commit/5eeaad7fb35f9a30f654e3bdeb6933daa7fd421d#diff-14d9864ac07456d554843dc2a3b174a4L179). To fix the issue, I started looking at all the places `I18n.t` was called from:\n\n```\nreferral --type call --exact-name I18n.t -c location,source\n```\n\nUnfortunately, this produces 250+ false positives because the predominant usage was to pass a single argument to `t`. Only calls with more than 1 argument are affected by this change. `--pattern` doesn't work very well in this codebase, because calls to `t` with multiple arguments are more likely to be multiline.\n\n```\nreferral --type call --exact-name I18n.t --arity 2+ -c location,source,arity\n```\n\nThis produced 27 results I could quickly skim through.\n\n\n## Options\n\nReferral provides a lot of options. The help output of `referral --help` will\nprint out the available options and their defaults:\n\n```\nUsage: referral [options] files\n    -v, --version                    Prints the version\n    -h, --help                       Prints this help\n    -n, --name [NAME]                Partial or complete name(s) to filter\n        --exact-name [NAME]          Exact name(s) to filter\n        --full-name [NAME]           Exact, fully-qualified name(s) to filter\n        --scope [SCOPE]              Scope(s) in which to filter (e.g. Hastack#hide)\n    -p, --pattern [PATTERN]          Regex pattern to filter\n    -t, --type [TYPES]               Include only certain types. See Referral::TOKEN_TYPES.\n        --arity [ARITY]              Number of arguments to a method call.  (e.g. 2+)\n        --include-unnamed            Include reference without identifiers (default: false)\n    -s, --sort {file,scope}          (default: file). See Referral::SORT_FUNCTIONS\n        --print-headers              Print header names (default: false)\n    -c, --columns [COL1,COL2,COL3]   (default: location,type,scope,name). See Referral::COLUMN_FUNCTIONS\n    -d, --delimiter [DELIM]          String separating columns (default: ' ')\n```\n\nA few things to note:\n\n* Each of `--name`, `--exact-name`, `--full-name`, `--scope`, `--type`, and `--columns`\n  accept comma-separated arrays (e.g. `--name foo,bar,baz`)\n\n* `--arity` accepts a number with an optional `+` or `-`.\n  *  `--arity 0`  Match calls with 0 arguments\n  *  `--arity 1+` Match calls with 1 or more arguments\n  *  `--arity 1-` Match calls with 1 or fewer arguments\n\n* You can browse available sort functions [in\n  Referral::SORT_FUNCTIONS](/lib/referral/sorts_tokens.rb) for use with\n  `--sort`. Each key is the name to be specified on the command line. (If you're\n  feeling adventurous, we've left the hash unfrozen so you can define your own\n  custom sorts dynamically, but YMMV.)\n\n* Just like sort functions, you can find the available column types [in\n  Referral::COLUMN_FUNCTIONS](/lib/referral/prints_results.rb) when passing a\n  comma-separated list to `--column`. (This hash has\n  also been left mutable for you, dear user.)\n\n* The types of AST nodes that Referral supports can be found [in\n  Referral::TOKEN_TYPES](/lib/referral/token_types.rb) when filtering to\n  certain definition \u0026 reference types with `--type`\n\n* Note that the columns `git_sha`, `git_author`, `git_commit_at` and the sort\n  functions `most_recent_commit` and `least_recent_commit` will slow things down\n  a bit, by invoking `git-blame` for each file included in the filtered\n  results\n\n* The `source` column and `--pattern` options will read each file in\n  the result set twice: once when parsing the AST, and again when printing\n  results\n\n## Running with Ruby version managers\n\nReferral requires Ruby version \u003e= 2.6, but your codebase may be running on something\nolder.  You have a few options for using `referral`.  You could 1) change your project's\nruby while you run `referral` and then change it back, but this seems cumbersome and\nlikely to cause annoyance.  There are better ways.\n\n## Run from outside your project's working directory\n\nIf you `cd ..` from your project's working directory (assuming in that context\nyou are running Ruby 2.6.x), you can run `referral` commands on your codebase by passing\nthe path to that codebase to referral:\n\n```\n$ referral MyAwesomeProject/\n```\n\nThis works for many of `referrals` features, but isn't ideal when it comes to git;\ncolumns like `git_sha`, `git_author` or `git_commit_at` will show empty results.\n\n## Running with `rbenv`\n\nIf you're using `rbenv`, you _could_ temporarily switch your project's ruby to 2.6.x,\nbut you'd have to remember to switch it back again before running any of the code in\nthe project.  To instantaneously switch to 2.6 and then back again (after the `referral`\ncommand finishes), do this (from your `MyAwsomeProject` directory):\n\n```\n$ RBENV_VERSION=2.6.3 referral\n```\n\n## Running with RVM\n\nThe corresponding way to do this with `rvm` would be:\n\n```\n$ rvm 2.6.3 do referral\n```\n\n## Code of Conduct\n\nThis project follows Test Double's [code of\nconduct](https://testdouble.com/code-of-conduct) for all community interactions,\nincluding (but not limited to) one-on-one communications, public posts/comments,\ncode reviews, pull requests, and GitHub issues. If violations occur, Test Double\nwill take any action they deem appropriate for the infraction, up to and\nincluding blocking a user from the organization's repositories.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftestdouble%2Freferral","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftestdouble%2Freferral","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftestdouble%2Freferral/lists"}