v1 Upgrade Notes

Cop Upgrade guide

Your custom cops should continue to work in v1.

Nevertheless it is suggested that you tweak them to use the v1 API by following the following steps:

1) Your class should inherit from RuboCop::Cop::Base instead of RuboCop::Cop::Cop.

2) Locate your calls to add_offense and make sure that you pass as the first argument either an AST::Node, a ::Parser::Source::Comment or a ::Parser::Source::Range, and no location: named parameter.

Example:

# Before
class MySillyCop < Cop
  def on_send(node)
    if node.method_name == :-
      add_offense(node, location: :selector, message: "Be positive")
    end
  end
end

# After
class MySillyCop < Base
  def on_send(node)
    if node.method_name == :-
      add_offense(node.loc.selector, message: "Be positive")
    end
  end
end

If your class supports autocorrection

Your class must extend AutoCorrector.

The corrector is now yielded from add_offense. Move the code of your method autocorrect in that block and do not wrap your correction in a lambda. Corrector are more powerful and can now be `merge`d.

Example:

# Before
class MySillyCorrectingCop < Cop
  def on_send(node)
    if node.method_name == :-
      add_offense(node, location: :selector, message: 'Be positive')
    end
  end

  def autocorrect(node)
    lambda do |corrector|
      corrector.replace(node.loc.selector, '+')
    end
  end
end

# After
class MySillyCorrectingCop < Base
  extend AutoCorrector

  def on_send(node)
    if node.method_name == :-
      add_offense(node.loc.selector, message: 'Be positive') do |corrector|
        corrector.replace(node.loc.selector, '+')
      end
    end
  end
end

Instance variables

Do not use RuboCop’s internal instance variables. If you used @processed_source, use processed_source. If you have a need to access an instance variable, open an issue with your use case.

By default, a Cop instance will be called only once for a given processed_source, so instance variables will be uninitialized when the investigation starts. Using @cache ||= …​ is fine. If you want to initialize some instance variable, the callback on_new_investigation is the best place to do so.

class MyCachingCop < Base
  def on_send(node)
    if my_cached_data[node]
      @counts(node.method_name) += 1
      #...
    end
  end

  # One way:
  def my_cached_data
    @data ||= processed_source.comments.map { # ... }
  end

  # Another way:
  def on_new_investigation
    @counts = Hash.new(0)
    super  # Be nice and call super for callback
  end
end

Other API changes

If your cop uses investigate, investigate_post_walk, join_force?, or internal classes like Corrector, Commissioner, Team, these have changed. See the Detailed API Changes.

Upgrading specs

It is highly recommended you use expect_offense / expect_correction / expect_no_offense in your specs, e.g.:

require 'rubocop/rspec/support'

RSpec.describe RuboCop::Cop::Custom::MySillyCorrectingCop, :config do
  # No need for `let(:cop)`
  it 'is positive' do
    expect_offense(<<~RUBY)
      42 + 2 - 2
             ^ Be positive
    RUBY

    expect_correction(<<~RUBY)
      42 + 2 + 2
    RUBY
  end

  it 'does not register an offense for calls to `despair`' do
    expect_no_offenses(<<~RUBY)
      "don't".despair
    RUBY
  end
end

In the unlikely case where you use the class RuboCop::Cop::Corrector directly, it has changed a bit but you can ease your transition with RuboCop::Cop::Legacy::Corrector that is meant to be somewhat backwards compatible. You will need to require 'rubocop/cop/legacy/corrector'.

Detailed API Changes

This section lists all changes (big or small) to the API. It is meant for maintainers of the nuts & bolts of RuboCop; most cop writers will not be impacted by these and are thus not the target audience.

Base class

Legacy: Cops inherit from Cop::Cop.

Current: Cops inherit from Cop::Base. Having a different base class makes the implementation much cleaner and makes it easy to signal which API is being used. Cop::Cop inherits from Cop::Base and refines some methods for backward compatibility.

add_offense API

arguments

Legacy: interface allowed for a node, with an optional location (symbol or range) or a range with a mandatory range as the location. Some cops were abusing the node argument and passing very different things.

Current: pass a range (or node as a shortcut for node.loc.expression), no location:. No abuse tolerated.

deduping changes

Both dedupe on range and won’t process the duplicated offenses at all.

Legacy: if offenses on same node but different range: considered as multiple offenses but a single autocorrect call.

Current: not applicable and not needed with autocorrection’s API.

yield

Both yield under the same conditions (unless cop is disabled for that line), but:

Legacy: yields after offense added to #offenses

Current: yields before offense is added to #offenses.

Even the legacy mode yields a corrector, but if a developer uses it an error will be raised asking her to inherit from Cop::Base instead.

Autocorrection

#autocorrect

Legacy: calls autocorrect unless it is disabled / autocorrect is off.

Current: yields a corrector unless it is disabled. The corrector will be ignored if autocorrecting is off, etc. No support for autocorrect method, but a warning is issued if that method is still defined.

Empty corrections

Legacy: autocorrect could return nil / false in cases where it couldn’t actually make a correction.

Current: No special API. Cases where no corrections are made are automatically detected.

Correction timing

Legacy: the lambda was called only later in the process, and only under specific conditions (if the autocorrect setting is turned on, etc.)

Current: correction is built immediately (assuming the cop isn’t disabled for the line) and applied later in the process.

Exception handling

Both: Commissioner will rescue all StandardErrors during analysis (unless option[:raise_error]) and store a corresponding ErrorWithAnalyzedFileLocation in its error list. This is done when calling the cop’s on_send & al., or when calling investigate / investigate_post_walk callback.

Legacy: autocorrecting cops were treating errors differently depending on when they occurred. Some errors were silently ignored. Others were rescued as above. Others crashed. Some code in Team would rescue errors and add them to the list of errors but I don’t think the code worked.

Current: Team no longer has any special error handling to do as potential exceptions happen when Commissioner is running.

Other error handling

Legacy: Clobbering errors are silently ignored. Calling insert_before with ranges that extend beyond the source code was silently fixed.

Current: Such errors are not ignored. It is still ok that a given Cop’s corrections clobber another Cop’s, but any given Cop should not issue corrections that clobber each other, or with invalid ranges, otherwise these will be listed in the processing errors.

#corrections

Legacy: Corrections were held in #corrections as an array of lambdas. A proxy was written to maintain compatibility with cop.corrections << ..., cop.corrections.concat ..., etc.

Current: Corrections are held in current_corrector, a Corrector which inherits from Source::TreeRewriter.

#support_autocorrect?

Legacy: was an instance method.

Current: now a class method.

Joining forces

Legacy: join_force?(force_class) was called with every force class

Current: self.joining_forces is now used to return the force (or an array of forces) to join.

Cop persistence

Cops can now be persisted between files. By default new cop instances are created for each source. See support_multiple_source? documentation.

Internal classes

Corrector

Legacy: initialize accepted a second argument (an array of lambdas). Available through Legacy::Corrector if needed.

Current: derives from parser’s `TreeRewriter. No second argument to initialize; not needed as correctors can be merged.

Commissioner & Team

Refactored for better separation of concern, being reusable, better result reporting and better error handling.

Misc API changes

  • internal API clarified for Commissioner. It calls begin_investigation and receives the results in complete_investigation.

  • New method add_global_offense for offenses that are not attached to a location in particular.

  • #offenses: No longer accessible.

  • Callbacks investigate(processed_source) and investigate_post_walk(processed_source) are renamed on_new_investigation and on_investigation_end and don’t accept an argument; all on_ callbacks should rely on processed_source.

  • #find_location is deprecated.

  • Correction is deprecated.

  • A few registry access methods were moved from Cop to Registry both for correctness (e.g. MyCop.qualified_cop_name did not work nor made sense) and so that Cop::Cop no longer holds any necessary code anymore. Backwards compatibility is maintained.

    • Cop.registry => Registry.global

    • Cop.all => Registry.all

    • Cop.qualified_cop_name => Registry.qualified_cop_name

  • The ConfigurableMax mixin for tracking exclude limits of configuration options is deprecated. Use exclude_limit ParameterName instead.