Class: Wrong::Chunk

Inherits:
Object show all
Includes:
Capturing
Defined in:
lib/wrong/chunk.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Capturing

#capturing

Constructor Details

#initialize(file, line_number, &block) ⇒ Chunk

line parameter is 1-based



32
33
34
35
36
# File 'lib/wrong/chunk.rb', line 32

def initialize(file, line_number, &block)
  @file = file
  @line_number = line_number.to_i
  @block = block
end

Instance Attribute Details

#blockObject (readonly)

Returns the value of attribute block.



29
30
31
# File 'lib/wrong/chunk.rb', line 29

def block
  @block
end

#fileObject (readonly)

Returns the value of attribute file.



29
30
31
# File 'lib/wrong/chunk.rb', line 29

def file
  @file
end

#line_numberObject (readonly)

Returns the value of attribute line_number.



29
30
31
# File 'lib/wrong/chunk.rb', line 29

def line_number
  @line_number
end

Class Method Details

.from_block(block, depth = 0) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/wrong/chunk.rb', line 11

def self.from_block(block, depth = 0)

  as_proc = block.to_proc
  file, line =
          if as_proc.respond_to? :source_location
            # in Ruby 1.9, or with Sourcify, it reads the source location from the block
            as_proc.source_location
          else
            # in Ruby 1.8, it reads the source location from the call stack
            relevant_caller = caller[depth]
            relevant_caller.split(":")
          end

  new(file, line, &block)
end

Instance Method Details

#build_sexpObject



50
51
52
53
54
55
56
# File 'lib/wrong/chunk.rb', line 50

def build_sexp
  glom(if @file == "(irb)"
         IRB.CurrentContext.all_lines
       else
         read_source_file(@file)
       end)
end

#claimObject

The claim is the part of the assertion inside the curly braces. E.g. for “assert { x == 5 }” the claim is “x == 5”



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/wrong/chunk.rb', line 89

def claim
  sexp()

  if @sexp.nil?
    raise "Could not parse #{location}"
  else
    assertion = @sexp.assertion
    statement = assertion && assertion[3]
    if statement.nil?
      @sexp
#          raise "Could not find assertion in #{location}\n\t#{@chunk.strip}\n\t#{@sexp}"
    else
      statement
    end
  end
end

#codeObject



106
107
108
109
110
111
112
# File 'lib/wrong/chunk.rb', line 106

def code
  self.claim.to_ruby
rescue => e
  # note: this is untested; it's to recover from when we can't locate the code
  message = "Failed at #{file}:#{line_number} [couldn't retrieve source code due to #{e.inspect}]"
  raise message
end

#glom(source) ⇒ Object

Algorithm:

  • try to parse the starting line

  • if it parses OK, then we’re done!

  • if not, then glom the next line and try again

  • repeat until it parses or we’re out of lines



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/wrong/chunk.rb', line 67

def glom(source)
  lines = source.split("\n")
  @parser ||= RubyParser.new
  @chunk = nil
  c = 0
  sexp = nil
  while sexp.nil? && line_index + c < lines.size
    begin
      @chunk = lines[line_index..line_index+c].join("\n")
      capturing(:stderr) do  # new RubyParser is too loud
        sexp = @parser.parse(@chunk)
      end
    rescue Racc::ParseError => e
      # loop and try again
      c += 1
    end
  end
  sexp
end

#line_indexObject



38
39
40
# File 'lib/wrong/chunk.rb', line 38

def line_index
  @line_number - 1
end

#locationObject



42
43
44
# File 'lib/wrong/chunk.rb', line 42

def location
  "#{@file}:#{@line_number}"
end

#parts(sexp = nil) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/wrong/chunk.rb', line 114

def parts(sexp = nil)
  if sexp.nil?
    parts(self.claim).compact.uniq
  else
    # todo: extract some of this into Sexp
    parts_list = []
    begin
      unless [:arglist, :lasgn, :iter].include? sexp.first
        code = sexp.to_ruby.strip
        parts_list << code unless code == "" || parts_list.include?(code)
      end
    rescue => e
      puts "#{e.class}: #{e.message}"
      puts e.backtrace.join("\n")
    end

    if sexp.first == :iter
      sexp.delete_at(1) # remove the method-call-sans-block subnode
    end

    sexp.each do |sub|
      if sub.is_a?(Sexp)
        parts_list += parts(sub)
      end
    end

    parts_list
  end
end

#read_source_file(file) ⇒ Object



58
59
60
# File 'lib/wrong/chunk.rb', line 58

def read_source_file(file)
  Config.read_here_or_higher(file)
end

#sexpObject



46
47
48
# File 'lib/wrong/chunk.rb', line 46

def sexp
  @sexp ||= build_sexp
end