docs/modules/ROOT/pages/v1_upgrade_notes.adoc
= v1 Upgrade Notes
:doctype: book
== 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.
[discrete]
==== Example:
[source,ruby]
----
# 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:
[source,ruby]
----
# 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.
[source,ruby]
----
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.:
[source,ruby]
----
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 ``StandardError``s 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.