Skip to content

Commit

Permalink
[Fixes rubocop#8286] Allow abbreviated offense messages with `expect_…
Browse files Browse the repository at this point in the history
…offense`
  • Loading branch information
marcandre committed Jul 10, 2020
1 parent 8fafbaf commit edabac0
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -10,6 +10,7 @@
* [#7736](https://github.com/rubocop-hq/rubocop/issues/7736): Add new `Style/CaseLikeIf` cop. ([@fatkodima][])
* [#4286](https://github.com/rubocop-hq/rubocop/issues/4286): Add new `Style/HashAsLastArrayItem` cop. ([@fatkodima][])
* [#8247](https://github.com/rubocop-hq/rubocop/issues/8247): Add new `Style/HashLikeCase` cop. ([@fatkodima][])
* [#8286](https://github.com/rubocop-hq/rubocop/issues/8286): Internal method `expect_offense` allows abbreviated offense messages. ([@marcandre][])

### Bug fixes

Expand Down
22 changes: 22 additions & 0 deletions docs/modules/ROOT/pages/development.adoc
Expand Up @@ -264,6 +264,28 @@ describe RuboCop::Cop::Style::SimplifyNotEmptyWithAny, :config do
end
----

If your code has variables of different lengths, you can use `%{foo}`,
`^{foo}`, and `_{foo}` to format your template; you can also abbreviate
offense messages with `[...]`:

[source,ruby]
----
%w[raise fail].each do |keyword|
expect_offense(<<~RUBY, keyword: keyword)
%{keyword}(RuntimeError, msg)
^{keyword}^^^^^^^^^^^^^^^^^^^ Redundant `RuntimeError` argument [...]
RUBY
%w[has_one has_many].each do |type|
expect_offense(<<~RUBY, type: type)
class Book
%{type} :chapter, foreign_key: 'book_id'
_{type} ^^^^^^^^^^^^^^^^^^^^^^ Specifying the default [...]
end
RUBY
end
----

=== Auto-correct

The auto-correct can help humans automatically fix offenses that have been detected.
Expand Down
34 changes: 29 additions & 5 deletions lib/rubocop/rspec/expect_offense.rb
Expand Up @@ -73,19 +73,20 @@ module RSpec
# expect_no_corrections
#
# If your code has variables of different lengths, you can use `%{foo}`,
# `^{foo}`, and `_{foo}` to format your template:
# `^{foo}`, and `_{foo}` to format your template; you can also abbreviate
# offense messages with `[...]`:
#
# %w[raise fail].each do |keyword|
# expect_offense(<<~RUBY, keyword: keyword)
# %{keyword}(RuntimeError, msg)
# ^{keyword}^^^^^^^^^^^^^^^^^^^ Redundant `RuntimeError` argument can be removed.
# ^{keyword}^^^^^^^^^^^^^^^^^^^ Redundant `RuntimeError` argument [...]
# RUBY
#
# %w[has_one has_many].each do |type|
# expect_offense(<<~RUBY, type: type)
# class Book
# %{type} :chapter, foreign_key: 'book_id'
# _{type} ^^^^^^^^^^^^^^^^^^^^^^ Specifying the default value is redundant.
# _{type} ^^^^^^^^^^^^^^^^^^^^^^ Specifying the default [...]
# end
# RUBY
# end
Expand Down Expand Up @@ -133,7 +134,7 @@ def expect_offense(source, file = nil, severity: nil, **replacements)
actual_annotations =
expected_annotations.with_offense_annotations(offenses)

expect(actual_annotations.to_s).to eq(expected_annotations.to_s)
expect(actual_annotations).to eq(expected_annotations)
expect(offenses.map(&:severity).uniq).to eq([severity]) if severity
end

Expand Down Expand Up @@ -189,6 +190,7 @@ def expect_no_offenses(source, file = nil)
# Parsed representation of code annotated with the `^^^ Message` style
class AnnotatedSource
ANNOTATION_PATTERN = /\A\s*(\^+|\^{}) /.freeze
ABBREV = "[...]\n"

# @param annotated_source [String] string passed to the matchers
#
Expand Down Expand Up @@ -222,6 +224,27 @@ def initialize(lines, annotations)
@annotations = annotations.sort.freeze
end

def ==(other)
other.is_a?(self.class) &&
other.lines == lines &&
match_annotations?(other)
end

# Dirty hack: expectations with [...] are rewritten when they match
# This way the diff is clean.
def match_annotations?(other)
annotations.zip(other.annotations) do |(_actual_line, actual_annotation),
(_expected_line, expected_annotation)|
if expected_annotation.end_with?(ABBREV)
if actual_annotation.start_with?(expected_annotation[0...-ABBREV.length])
expected_annotation.replace(actual_annotation)
end
end
end

annotations == other.annotations
end

# Construct annotated source string (like what we parse)
#
# Reconstruct a deterministic annotated source string. This is
Expand Down Expand Up @@ -254,6 +277,7 @@ def to_s

reconstructed.join
end
alias inspect to_s

# Return the plain source code without annotations
#
Expand All @@ -280,7 +304,7 @@ def with_offense_annotations(offenses)
self.class.new(lines, offense_annotations)
end

private
protected

attr_reader :lines, :annotations
end
Expand Down
6 changes: 3 additions & 3 deletions spec/rubocop/cop/bundler/insecure_protocol_source_spec.rb
Expand Up @@ -4,7 +4,7 @@
it 'registers an offense when using `source :gemcutter`' do
expect_offense(<<~RUBY)
source :gemcutter
^^^^^^^^^^ The source `:gemcutter` is deprecated because HTTP requests are insecure. Please change your source to 'https://rubygems.org' if possible, or 'http://rubygems.org' if not.
^^^^^^^^^^ The source `:gemcutter` is deprecated [...]
RUBY

expect_correction(<<~RUBY)
Expand All @@ -15,7 +15,7 @@
it 'registers an offense when using `source :rubygems`' do
expect_offense(<<~RUBY)
source :rubygems
^^^^^^^^^ The source `:rubygems` is deprecated because HTTP requests are insecure. Please change your source to 'https://rubygems.org' if possible, or 'http://rubygems.org' if not.
^^^^^^^^^ The source `:rubygems` is deprecated [...]
RUBY

expect_correction(<<~RUBY)
Expand All @@ -26,7 +26,7 @@
it 'registers an offense when using `source :rubyforge`' do
expect_offense(<<~RUBY)
source :rubyforge
^^^^^^^^^^ The source `:rubyforge` is deprecated because HTTP requests are insecure. Please change your source to 'https://rubygems.org' if possible, or 'http://rubygems.org' if not.
^^^^^^^^^^ The source `:rubyforge` is deprecated [...]
RUBY

expect_correction(<<~RUBY)
Expand Down

0 comments on commit edabac0

Please sign in to comment.