This file provides guidance to AI coding agents when working with code in this repository.
PR titles must use Conventional Commits format (enforced by CI):
feat:— new cop or feature (minor bump)fix:— bug fix (patch bump)feat!:— breaking change (major bump)chore:,docs:,ci:— no release
Releases are automated via release-please: merge the auto-generated Release PR to publish.
rubocop-gusto is a RubyGem that ships Gusto's custom RuboCop cops and a shared RuboCop configuration. It integrates via the lint_roller plugin interface, so consuming projects add it to their plugins: list in .rubocop.yml.
# Lint
bundle exec rubocop
# Run all tests
bundle exec rspec
# Run a single test file
bundle exec rspec spec/rubocop/cop/gusto/some_cop_spec.rb
# Run a single example by line number
bundle exec rspec spec/rubocop/cop/gusto/some_cop_spec.rb:42
# Sort cops in a rubocop yml file (required after adding a new cop entry to config/default.yml)
bundle exec rubocop-gusto sort config/default.yml
# Initialize rubocop-gusto in a consuming project
bundle exec rubocop-gusto initlib/rubocop/cop/gusto/— Custom Gusto cops (one file per cop)lib/rubocop/cop/rack/— Custom cops scoped to Rack middleware patternslib/rubocop/cop/internal_affairs/— Cops that lint this gem's own cops (enforced in CI on this repo)lib/rubocop/gusto/— Supporting library code:CLI,Init,ConfigYml,Plugin,versionconfig/default.yml— The shared RuboCop configuration distributed with this gemconfig/rails.yml— Additional Rails-specific configuration (included byinitwhen Rails is detected)spec/rubocop/cop/— Mirrored spec structure matchinglib/rubocop/cop/
Reference docs:
- RuboCop cop development guide
- rubocop-rspec development guide — naming conventions are broadly applicable beyond RSpec cops
- rubocop-ast node types
- rubocop-ast Node API
- Node pattern syntax — the
def_node_matcheranddef_node_searchsections are of particular interest
- Create
lib/rubocop/cop/gusto/<cop_name>.rbwith classRuboCop::Cop::Gusto::<CopName> < Base. - If the cop uses
on_sendorafter_send, declareRESTRICT_ON_SEND = %i[my_method_name].freeze— theInternalAffairs::RequireRestrictOnSendcop enforces this. - Use
def_node_matcher/def_node_searchwith# @!methodYARD annotations for all AST pattern matchers. Two important behaviors to know:- Any cop that implements
on_sendshould also handle safe navigation (&.) — addalias_method :on_csend, :on_sendunless the cop explicitly does not apply to safe navigation calls. def_node_search :name?(with a?suffix) returnstrue/false, not an Enumerator. Use the result directly as a boolean. Without the?suffix it returns an Enumerator.
- Any cop that implements
- Add an entry to
config/default.ymlwith at minimum aDescription:key, then runbundle exec rubocop-gusto sort config/default.yml. - Create a corresponding spec in
spec/rubocop/cop/gusto/<cop_name>_spec.rb.
100% line and branch coverage is required. .simplecov enforces this on every run — a branch coverage failure will abort the suite. Never use :nocov:.
Specs use RuboCop::RSpec::ExpectOffense helpers (included globally via spec_helper.rb):
RSpec.describe RuboCop::Cop::Gusto::MyCop, :config do
it "registers an offense" do
expect_offense(<<~RUBY)
bad_method_call
^^^^^^^^^^^^^^^ Use `good_method_call` instead of `bad_method_call`.
RUBY
end
it "does not register an offense" do
expect_no_offenses(<<~RUBY)
good_method_call
RUBY
end
endThe :config metadata wires up a default RuboCop::Config instance. To pass cop-specific configuration, override let(:cop_config) with a plain hash:
let(:cop_config) { { "WorkerModules" => ["MyApp::Worker"] } }RuboCop::Gusto::ConfigYml reads and writes .rubocop.yml while preserving comments. It parses the file into "preamble" blocks (e.g. inherit_gem, plugins) and "cops" blocks, and can sort, add plugins, or add inherit_gem entries without clobbering existing content. Use it when implementing CLI commands that modify a project's .rubocop.yml — see lib/rubocop/gusto/init.rb for example usage.
Run once after cloning to enable the pre-commit hook:
git config core.hooksPath script/githooks