• Stars
    star
    343
  • Rank 119,069 (Top 3 %)
  • Language
    Ruby
  • License
    MIT License
  • Created almost 5 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

🕵️‍♀️ Find, filter, and sort your Ruby code's definitions & references

Referral 🔍

Referral is a CLI to help you undertake complex analyses and refactorings of Ruby codebases. It finds, filters, and sorts the definitions & references of most types of Ruby identifiers (e.g. classes, methods, and variables) throughout your code.

Think of referral as a toolkit for tracking down references in your code for any number of purposes, offering a boatload of command-line options to enable you to efficiently accomplish things like:

  • Size up a codebase by gathering basic statistics and spotting usage hotspots
  • Build a to-do list to help you manage a large or complex refactor
  • Quickly make a list of every call to a deprecated method, rather than wait for warnings at runtime
  • Get a sense for how many callers would be impacted if you were to delete a method
  • Before renaming a module, verify there aren't already any other modules with the new name
  • Verify that you removed every reference to a deleted class before you merge
  • Identify dead code, like method definitions that aren't invoked anywhere
  • Catch references that haven't been updated since a change that affected them (according to git-blame)

Because Referral is powered by the introspection made possible by Ruby 2.6's RubyVM::AbstractSyntaxTree API, it must be run with Ruby 2.6 or later. Nevertheless, it can often analyze code listings designed to run on older Rubies.

Install

From the command line:

$ gem install referral

Or in your Gemfile

gem "referral", require: false, group: :development

How to use Referral

Basic usage

At its most basic, you can just run referral and it'll scan **/*.rb from the current working directory and print every reference it finds:

$ referral
app/channels/application_cable/channel.rb:1:0: module  ApplicationCable
app/channels/application_cable/channel.rb:2:2: class ApplicationCable Channel
app/channels/application_cable/channel.rb:2:18: constant ApplicationCable::Channel ActionCable::Channel::Base
# … and then another 2400 lines (which you can easily count with `referral | wc -l`)

By default, Referral will sort entries by file, line, and column. Default output is broken into 4 columns: location, type, scope, and name.

If you'd like to scan a subset of files, you can pass a final argument with file paths and directories. For example, if you only wanted to search code in the top-level of app/lib you could run referral app/lib/*.rb. Or, if you wanted to include subdirectories, referral app/lib.

Everything above can be custom-tailored to your purposes, so let's work through some example recipes to teach you Referral's various features below. (Or, feel free to skip to the full list of options).

Recipe: build a refactoring to-do spreadsheet

When I'm undergoing a large refactor, I like to start by grepping around for all the obvious definitions and references that might be affected. Suppose I'm going to make major changes to my User class. I might use Referral's --exact-name filter like this:

referral --exact-name User,user,@user,@current_user

[Fun fact: if I'd have wanted to match on partial names, I could have used the looser --name, or for fully-qualified names (e.g. API::User), the stricter --full-name option.]

Next, I usually find it easiest to work through a large refactor file-by-file, but in certain cases where I'm looking for a specific type of reference, it makes more sense to sort by the fully-qualified scope, which can be done with --sort scope:

referral --exact-name User,user,@user,@current_user --sort scope

The above will sort results by their fully-qualified names (e.g. A::B#c), rather than their filenames.

Of course, if we want a checklist, the default output could be made a lot nicer for export to a spreadsheet app like Numbers. Here's how you might invoke referral to save a tab-separated-values (TSV) file:

referral --exact-name User,user,@user,@current_user --sort scope --print-headers --delimiter "\t" > user_refs.tsv

Where --print-headers prints an initial row of the selected column names, and --delimiter "\t" separates each field by a tab (making it easier to ingest for a spreadsheet app like Excel or Numbers), before being redirected to the file user_refs.tsv.

Now, to open it in Numbers, I'd run:

open -a Numbers user_refs.tsv

And you'll be greeted by a spreadsheet. And hey, why not throw a checkbox column on there while you're at it:

Screen Shot 2019-06-27 at 1 27 42 PM

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.

Recipe: detect references you forgot to update

When working in a large codebase, it can be really tough to figure out if you remembered to update every reference to a class or method across thousands of files, so Referral ships with the ability to get some basic information from git-blame, like this:

referral --column file,line,git_sha,git_author,git_commit_at,full_name

By setting --column to a comma-separated array that includes the above, Referral will print results that look like these:

test/lib/splits_furigana_test.rb 56 634edc04 [email protected] 2017-09-04T13:34:09Z SplitsFuriganaTest#test_nasty_edge_cases.assert_equal
test/lib/splits_furigana_test.rb 56 634edc04 [email protected] 2017-09-04T13:34:09Z h
test/lib/splits_furigana_test.rb 56 634edc04 [email protected] 2017-09-04T13:34:09Z @subject.call

[Warning: running git-blame on each file is, of course, a bit slow. Running this command on the KameSame codebase took 3 seconds of wall-time, compared to 0.7 seconds by default.]

And it gets better! Since we're already running blame, why not sort every line by its most and least recent commit time? You can! To list the least-recently-changed references first, add the option --sort least_recent_commit:

referral --sort least_recent_commit --column file,line,git_sha,git_author,git_commit_at,full_name

In my case, I see that my least-recently-updated Ruby reference is:

app/channels/application_cable/channel.rb 1  [email protected] 2017-08-20T14:59:35Z ApplicationCable

The inclusion of git-blame fields and sorting can be a powerful tool to spot-check a large refactor before deciding to merge it in.

Recipe: search for a regex pattern and print the source

Once in a while, I'll want to scan line-by-line in a codebase for lines that match a given pattern, and in those cases, the --pattern option and source column can be a big help.

Suppose I'm trying to size up a codebase by looking for how many methods appear to have a lot of arguments. While definitely imperfect and regex cannot parse context-free grammars, I can get a rough gist by searching for any lines that have 4 or more commas on them:

referral --pattern "/^([^,]*,){4,}[^,]*$/" -c location,source

Which would yield results like this one:

app/lib/card.rb:22:2:   def self.from_everything(id:, lesson_type:, item:, assignment:, meaning:)

Naturally, other programs like find could do this just as well, but the added ability to see & sort by when these lines were last updated in git might be interesting. Additionally, suppose you only wanted to find method definitions with a lot of (apparent) arguments? You could filter the matches down with --type instance_method,class_method, too, like this:

referral --pattern "/^([^,]*,){4,}[^,]*$/" -c location,git_commit_at,source -s most_recent_commit --type instance_method,class_method

In my results, I learned that as recently as June 6th, I wrote a very long method definition:

app/lib/presents_review_result.rb:60:2: 2019-06-02T02:38:01Z   def item_result(study_card_identifier, user, answer, item, learning, judgment, reward)

find couldn't have told me that (I don't think)!

Recipe: Find calls that have more than 1 argument

Recently I was upgrading the i18n gem and came across some bugs introduced by this change. To fix the issue, I started looking at all the places I18n.t was called from:

referral --type call --exact-name I18n.t -c location,source

Unfortunately, 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.

referral --type call --exact-name I18n.t --arity 2+ -c location,source,arity

This produced 27 results I could quickly skim through.

Options

Referral provides a lot of options. The help output of referral --help will print out the available options and their defaults:

Usage: referral [options] files
    -v, --version                    Prints the version
    -h, --help                       Prints this help
    -n, --name [NAME]                Partial or complete name(s) to filter
        --exact-name [NAME]          Exact name(s) to filter
        --full-name [NAME]           Exact, fully-qualified name(s) to filter
        --scope [SCOPE]              Scope(s) in which to filter (e.g. Hastack#hide)
    -p, --pattern [PATTERN]          Regex pattern to filter
    -t, --type [TYPES]               Include only certain types. See Referral::TOKEN_TYPES.
        --arity [ARITY]              Number of arguments to a method call.  (e.g. 2+)
        --include-unnamed            Include reference without identifiers (default: false)
    -s, --sort {file,scope}          (default: file). See Referral::SORT_FUNCTIONS
        --print-headers              Print header names (default: false)
    -c, --columns [COL1,COL2,COL3]   (default: location,type,scope,name). See Referral::COLUMN_FUNCTIONS
    -d, --delimiter [DELIM]          String separating columns (default: ' ')

A few things to note:

  • Each of --name, --exact-name, --full-name, --scope, --type, and --columns accept comma-separated arrays (e.g. --name foo,bar,baz)

  • --arity accepts a number with an optional + or -.

    • --arity 0 Match calls with 0 arguments
    • --arity 1+ Match calls with 1 or more arguments
    • --arity 1- Match calls with 1 or fewer arguments
  • You can browse available sort functions in Referral::SORT_FUNCTIONS for use with --sort. Each key is the name to be specified on the command line. (If you're feeling adventurous, we've left the hash unfrozen so you can define your own custom sorts dynamically, but YMMV.)

  • Just like sort functions, you can find the available column types in Referral::COLUMN_FUNCTIONS when passing a comma-separated list to --column. (This hash has also been left mutable for you, dear user.)

  • The types of AST nodes that Referral supports can be found in Referral::TOKEN_TYPES when filtering to certain definition & reference types with --type

  • Note that the columns git_sha, git_author, git_commit_at and the sort functions most_recent_commit and least_recent_commit will slow things down a bit, by invoking git-blame for each file included in the filtered results

  • The source column and --pattern options will read each file in the result set twice: once when parsing the AST, and again when printing results

Running with Ruby version managers

Referral requires Ruby version >= 2.6, but your codebase may be running on something older. You have a few options for using referral. You could 1) change your project's ruby while you run referral and then change it back, but this seems cumbersome and likely to cause annoyance. There are better ways.

Run from outside your project's working directory

If you cd .. from your project's working directory (assuming in that context you are running Ruby 2.6.x), you can run referral commands on your codebase by passing the path to that codebase to referral:

$ referral MyAwesomeProject/

This works for many of referrals features, but isn't ideal when it comes to git; columns like git_sha, git_author or git_commit_at will show empty results.

Running with rbenv

If you're using rbenv, you could temporarily switch your project's ruby to 2.6.x, but you'd have to remember to switch it back again before running any of the code in the project. To instantaneously switch to 2.6 and then back again (after the referral command finishes), do this (from your MyAwsomeProject directory):

$ RBENV_VERSION=2.6.3 referral

Running with RVM

The corresponding way to do this with rvm would be:

$ rvm 2.6.3 do referral

Code of Conduct

This project follows Test Double's code of conduct for all community interactions, including (but not limited to) one-on-one communications, public posts/comments, code reviews, pull requests, and GitHub issues. If violations occur, Test Double will take any action they deem appropriate for the infraction, up to and including blocking a user from the organization's repositories.

More Repositories

1

standard

🌟 Ruby Style Guide, with linter & automatic code fixer
Ruby
2,104
star
2

testdouble.js

A minimal test double library for TDD with JavaScript
JavaScript
1,407
star
3

suture

🏥 A Ruby gem that helps you refactor your legacy code
Ruby
1,401
star
4

contributing-tests

1,104
star
5

scripty

Because no one should be shell-scripting inside a JSON file.
JavaScript
957
star
6

test-smells

A workbook repository of example test smells and what to do about them.
JavaScript
421
star
7

jasmine-rails

A Jasmine runner for rails projects that's got you covered in both the terminal and the browser
JavaScript
378
star
8

cypress-rails

Helps you write Cypress tests of your Rails app
Ruby
312
star
9

good-migrations

Prevent Rails from auto-loading app/ code when running database migrations
Ruby
294
star
10

mocktail

🥃 Take your Ruby, and make it a double!
Ruby
273
star
11

static-rails

Build & serve static sites (e.g. Jekyll, Hugo) from your Rails app
Ruby
149
star
12

maybe_later

Run code after the current Rack response or Rails action completes
Ruby
132
star
13

time_up

⏱ Create and manage multiple timers to tell where your Ruby code's time is going
Ruby
117
star
14

teenytest

A very simple, zero-config test runner for Node.js
JavaScript
97
star
15

test_data

A fast & reliable system for managing your Rails application's test data
Ruby
95
star
16

put

Ruby
92
star
17

theredoc

Makes your multi-line JavaScript strings look good
JavaScript
79
star
18

quibble

Makes it easy to replace require'd dependencies.
JavaScript
78
star
19

react-decoupler

JavaScript
55
star
20

noncommittal

A gem that ensures test isolation by preventing your Rails tests from committing to the database
Ruby
46
star
21

real-world-testing-video

testdouble/real-world-testing + screencasts
JavaScript
40
star
22

clojurescript.csv

A ClojureScript library for reading and writing CSV
Clojure
37
star
23

testdouble-jest

A testdouble.js extension to add support for Jest module mocking
JavaScript
37
star
24

grunt-markdown-blog

Grunt task for building a blog with markdown posts & underscore templates
CoffeeScript
36
star
25

ought

A dumb assertion library with smart diffs for JavaScript
JavaScript
34
star
26

cypress-capybara

Capybara finders re-implemented as custom Cypress commands
JavaScript
33
star
27

minitest-suite

Re-order your Minitest suite into logical sub-suites/groups
Ruby
32
star
28

rust-ffi-example

An example project that shows how to use FFI between Rust and Unity.
Rust
31
star
29

gem_dating

How old is that anyway?
Ruby
29
star
30

rspec-graphql_response

Verify ruby-graphql responses with a :graphql spec type
Ruby
25
star
31

ecto_resource

A simple module to clear up the boilerplate of CRUD resources in Phoenix context files.
Elixir
22
star
32

java-testing-example

An example project that's configured for JUnit and Mocha
Java
20
star
33

real-world-testing

Workshop for Testing JavaScripts
JavaScript
17
star
34

unusual-spending

A code kata for outside-in TDD in Node.js
JavaScript
16
star
35

webpacker-assets-demo

A demo repo to show how to reference images and styles when using Webpacker instead of Sprockets
Ruby
13
star
36

javascript-testing-tactics

The Missing Manual for writing great JavaScript Testing
13
star
37

magic_email_demo

An example Rails app that implements passwordless authentication by emailing a magic link
Ruby
12
star
38

scheduled-merge

Merge PRs on a specified date using Labels
JavaScript
12
star
39

rust-ffi-complex-example

Follow-up project to shows how to use complex data structures between Unity and Rust.
Rust
12
star
40

todos

JavaScript
11
star
41

grunt-asset-fingerprint

CoffeeScript
9
star
42

covet

Instruct a remote Express app to stub APIs via HTTP requests
CoffeeScript
9
star
43

bored

Gives you ideas of stuff to do when you're bored
Ruby
8
star
44

rails-twitter-oauth-example

An example Rails app that implements log in to Twitter via OAuth
Ruby
8
star
45

javascript-tdd-examples

Yet another little toy repo of javascript tdd examples
JavaScript
8
star
46

baizen

BAI file format parser
Clojure
8
star
47

tiny_type

Fast, easy, and simple runtime type checking for Ruby
Ruby
8
star
48

halfpipe

A Pipedrive client for Ruby that doesn't do half of what you want it to 🛹
Ruby
7
star
49

forewarn

Configure method invocation warnings for deprecated or dangerous methods (e.g. mutable methods in default-frozen String literals in Ruby 3)
Ruby
7
star
50

grunt-jasmine-bundle

A "spec" grunt task for Jasmine that includes a standard pack of helpers (jasmine-given, jasmine-stealth, jasmine-only). Uses minijasminenode.
CoffeeScript
6
star
51

servme

gimme for integration tests
Ruby
6
star
52

intro-to-node

Introduction to Node.js Workshop
JavaScript
6
star
53

standardrb

You're probably in the wrong place. This is an alias for the gem standard, whose binary is standardrb
Ruby
6
star
54

bootboot-example

An example of using boot-boot.
Ruby
5
star
55

testdrivennode

Test Driven Node.js Precompiler for Codemash 2014
JavaScript
5
star
56

docunit

Makes sure the code examples in your docs actually work
CoffeeScript
5
star
57

railsconf-test-drive-javascript

JavaScript
5
star
58

json-to-svg-to-pdf

Converts JSON/CSON input through SVG templates and renders them to PDF using librsvg
JavaScript
5
star
59

imagemagick-macos-font-setup

Sets up user fonts for imagemagick on macOS
Shell
5
star
60

jasmine-before-all

Adds a done-friendly beforeAll global function to Jasmine
JavaScript
5
star
61

good-day

An example ember + active_model_serializers + rails + lineman app
JavaScript
5
star
62

sockem

A wrapper around the ActionCable JS client to ensure eventual delivery for requests
Ruby
5
star
63

satisfaction

Satisfaction tracker for your work!
Ruby
5
star
64

headerify

Browserify plugin to add a comment containing lib name, version, description, and homepage to the top of the bundle
JavaScript
4
star
65

SublimeLinter-contrib-standardrb

SublimeLinter 3 plugin for Ruby, using Standard, a wrapper for Rubocop.
Python
4
star
66

rails-upsert-all-demo

An example app that demos use of Rails 6 `upsert_all` method
Ruby
4
star
67

cobbler

A tool to generate résumés for Test Double agents.
JavaScript
3
star
68

react-d3-blog-example

Example for Blog Post
JavaScript
3
star
69

supertitle

Converts between subtitles and transcript formats
Ruby
3
star
70

time_traveler_demo

A Rails app that demoes time traveling both Ruby and Postgres in lock-step with one another
Ruby
3
star
71

devops-standards

Standard Auditing Tools for DevSecOps best practices
Python
3
star
72

least

A pager that can dynamically filter log lines
Ruby
3
star
73

defuse

An API to define and use JavaScript in a module-y way. And nothing else.
JavaScript
3
star
74

lockify

Ensure an async function does not run concurrently.
JavaScript
3
star
75

jasmine-matcher-wrapper

A utility to wrap Jasmine 1.x argument matchers for use under Jasmine 2.x
CoffeeScript
3
star
76

testdouble-nock

JavaScript
3
star
77

teenytest-promise

Promise support for asynchronous teenytest tests
JavaScript
3
star
78

npm-tree

Generates a tree of all the node.js modules depended on by a module
CoffeeScript
3
star
79

function-names-at-line

Name the functions found at a particular line number in some JavaScript source
JavaScript
2
star
80

tradecraft

CSS
2
star
81

course-cypress-intro-demo-app

Demo application to supplement Test Double's End-to-end Testing with Cypress intro video course
Ruby
2
star
82

standard-ruby-action

Ruby
2
star
83

rails-training-201

A demo app for Rails 201 students to build on!
Ruby
2
star
84

testdrivennode-frontend

JavaScript
2
star
85

yslow-grader

A little Node.js wrapper for YSlow for PhantomJS
CoffeeScript
2
star
86

ios-learnins

Objective-C
2
star
87

fetcher

Fetches things based on a JSON recipe hosted in a repository
CoffeeScript
2
star
88

backbone-fixins

Boilerplate that strengthens your backbone
JavaScript
2
star
89

ruby_rails_training_github

Ruby
1
star
90

prioritize-api

Elixir
1
star
91

baruco2014-angular

Ruby
1
star
92

oredev2014-angular

JavaScript
1
star
93

double-up

Slack scheduler to set up rotating brunch pairings
Ruby
1
star
94

elm-testdouble

A minimal test double library for TDD with Elm
Elm
1
star
95

doubot

test double's hubot
CoffeeScript
1
star
96

jasmine-example

JavaScript
1
star
97

arg-that

arg-that makes it easier to assert equality on complex objects
Ruby
1
star
98

react-routing-example

Example for screencast on client-side routing in React
CSS
1
star
99

cucumber-peel

Provides a CLI to search a project's step implementations for a given step
Ruby
1
star
100

test_rails_app

A starter Rails application to test your environment setup
Ruby
1
star