Class: RuboCop::RSpec::ExpectOffense::AnnotatedSource

Inherits:
Object
  • Object
show all
Defined in:
lib/rubocop/rspec/expect_offense.rb

Overview

Parsed representation of code annotated with the `^^^ Message` style

Constant Summary collapse

ANNOTATION_PATTERN =
/\A\s*(\^+|\^{}) /.freeze
ABBREV =
"[...]\n"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(lines, annotations) ⇒ AnnotatedSource

Note:

annotations are sorted so that reconstructing the annotation text via #to_s is deterministic

Returns a new instance of AnnotatedSource.

Parameters:

  • lines (Array<String>)
  • annotations (Array<(Integer, String)>)

    each entry is the annotated line number and the annotation text


240
241
242
243
# File 'lib/rubocop/rspec/expect_offense.rb', line 240

def initialize(lines, annotations)
  @lines       = lines.freeze
  @annotations = annotations.sort.freeze
end

Class Method Details

.parse(annotated_source) ⇒ AnnotatedSource

Separates annotation lines from source lines. Tracks the real source line number that each annotation corresponds to.

Parameters:

  • annotated_source (String)

    string passed to the matchers

Returns:


218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/rubocop/rspec/expect_offense.rb', line 218

def self.parse(annotated_source)
  source      = []
  annotations = []

  annotated_source.each_line do |source_line|
    if ANNOTATION_PATTERN.match?(source_line)
      annotations << [source.size, source_line]
    else
      source << source_line
    end
  end
  annotations.each { |a| a[0] = 1 } if source.empty?

  new(source, annotations)
end

Instance Method Details

#==(other) ⇒ Object


245
246
247
248
249
# File 'lib/rubocop/rspec/expect_offense.rb', line 245

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

#match_annotations?(other) ⇒ Boolean

Dirty hack: expectations with […] are rewritten when they match This way the diff is clean.

Returns:

  • (Boolean)

253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/rubocop/rspec/expect_offense.rb', line 253

def match_annotations?(other)
  annotations.zip(other.annotations) do |(_actual_line, actual_annotation),
                                         (_expected_line, expected_annotation)|
    if expected_annotation&.end_with?(ABBREV) &&
       actual_annotation.start_with?(expected_annotation[0...-ABBREV.length])

      expected_annotation.replace(actual_annotation)
    end
  end

  annotations == other.annotations
end

#plain_sourceString

Return the plain source code without annotations

Returns:


303
304
305
# File 'lib/rubocop/rspec/expect_offense.rb', line 303

def plain_source
  lines.join
end

#to_sString Also known as: inspect

Construct annotated source string (like what we parse)

Reconstruct a deterministic annotated source string. This is useful for eliminating semantically irrelevant annotation ordering differences.

Examples:

standardization


source1 = AnnotatedSource.parse(<<-RUBY)
line1
^ Annotation 1
 ^^ Annotation 2
RUBY

source2 = AnnotatedSource.parse(<<-RUBY)
line1
 ^^ Annotation 2
^ Annotation 1
RUBY

source1.to_s == source2.to_s # => true

Returns:


289
290
291
292
293
294
295
296
297
# File 'lib/rubocop/rspec/expect_offense.rb', line 289

def to_s
  reconstructed = lines.dup

  annotations.reverse_each do |line_number, annotation|
    reconstructed.insert(line_number, annotation)
  end

  reconstructed.join
end

#with_offense_annotations(offenses) ⇒ self

Annotate the source code with the RuboCop offenses provided

Parameters:

Returns:

  • (self)

312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/rubocop/rspec/expect_offense.rb', line 312

def with_offense_annotations(offenses)
  offense_annotations =
    offenses.map do |offense|
      indent     = ' ' * offense.column
      carets     = '^' * offense.column_length
      carets     = '^{}' if offense.column_length.zero?

      [offense.line, "#{indent}#{carets} #{offense.message}\n"]
    end

  self.class.new(lines, offense_annotations)
end