Class: SeeingIsBelieving::Code

Inherits:
Object
  • Object
show all
Defined in:
lib/seeing_is_believing/code.rb

Constant Summary collapse

InlineComment =
HashStruct.for :line_number, :whitespace_col, :whitespace, :text_col, :text, :full_range, :whitespace_range, :comment_range
Syntax =
HashStruct.for error_message: nil, line_number: nil do
  def valid?()   !error_message end
  def invalid?() !valid?        end
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(raw_code, name = nil) ⇒ Code

Returns a new instance of Code.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/seeing_is_believing/code.rb', line 23

def initialize(raw_code, name=nil)
  raw_code[-1] == "\n" || raise(SyntaxError, "Code must end in a newline for the sake of consistency (sanity)")
  @raw             = raw_code
  @buffer          = Parser::Source::Buffer.new(name||"SeeingIsBelieving")
  @buffer.source   = raw
  @rewriter        = Parser::Source::TreeRewriter.new buffer
  builder          = Parser::Builders::Default.new.tap { |b| b.emit_file_line_as_literals = false }
  @parser          = Parser::CurrentRuby.new builder
  @syntax          = Syntax.new
  parser.diagnostics.consumer = lambda do |diagnostic|
    if :fatal == diagnostic.level || :error == diagnostic.level
      @syntax = Syntax.new error_message: diagnostic.message, line_number: index_to_linenum(diagnostic.location.begin_pos)
    end
  end
  @root, @raw_comments, @tokens = parser.tokenize(@buffer, true)
  @body_range                   = body_range_from_tokens(@tokens)
  @inline_comments              = raw_comments.select(&:inline?).map { |c| wrap_comment c }
  @root                       ||= null_node
end

Instance Attribute Details

#body_rangeObject (readonly)

Returns the value of attribute body_range.



21
22
23
# File 'lib/seeing_is_believing/code.rb', line 21

def body_range
  @body_range
end

#bufferObject (readonly)

Returns the value of attribute buffer.



21
22
23
# File 'lib/seeing_is_believing/code.rb', line 21

def buffer
  @buffer
end

#inline_commentsObject (readonly)

Returns the value of attribute inline_comments.



21
22
23
# File 'lib/seeing_is_believing/code.rb', line 21

def inline_comments
  @inline_comments
end

#parserObject (readonly)

Returns the value of attribute parser.



21
22
23
# File 'lib/seeing_is_believing/code.rb', line 21

def parser
  @parser
end

#rawObject (readonly)

Returns the value of attribute raw.



21
22
23
# File 'lib/seeing_is_believing/code.rb', line 21

def raw
  @raw
end

#raw_commentsObject (readonly)

Returns the value of attribute raw_comments.



21
22
23
# File 'lib/seeing_is_believing/code.rb', line 21

def raw_comments
  @raw_comments
end

#rewriterObject (readonly)

Returns the value of attribute rewriter.



21
22
23
# File 'lib/seeing_is_believing/code.rb', line 21

def rewriter
  @rewriter
end

#rootObject (readonly)

Returns the value of attribute root.



21
22
23
# File 'lib/seeing_is_believing/code.rb', line 21

def root
  @root
end

#syntaxObject (readonly)

Returns the value of attribute syntax.



21
22
23
# File 'lib/seeing_is_believing/code.rb', line 21

def syntax
  @syntax
end

Instance Method Details

#heredoc?(ast) ⇒ Boolean

Returns:

  • (Boolean)


56
57
58
59
60
61
62
63
64
65
# File 'lib/seeing_is_believing/code.rb', line 56

def heredoc?(ast)
  # some strings are fucking weird.
  # e.g. the "1" in `%w[1]` returns nil for ast.location.begin
  # and `__FILE__` is a string whose location is a Parser::Source::Map instead of a Parser::Source::Map::Collection,
  # so it has no #begin
  ast.kind_of?(Parser::AST::Node)           &&
    (ast.type == :dstr || ast.type == :str) &&
    (location = ast.location)               &&
    (location.kind_of? Parser::Source::Map::Heredoc)
end

#index_to_linenum(char_index) ⇒ Object



47
48
49
# File 'lib/seeing_is_believing/code.rb', line 47

def index_to_linenum(char_index)
  line_indexes.index { |line_index| char_index < line_index } || line_indexes.size
end

#indexes_of_ors_at_eolObject



91
92
93
94
95
96
97
# File 'lib/seeing_is_believing/code.rb', line 91

def indexes_of_ors_at_eol
  Set.new(
    @tokens.select { |type, *| type == :tGVAR }
           .select { |_, (var, range)| var == '$\\'.freeze }
           .map    { |_, (var, range)| range.end_pos }
  )
end

#line_indexesObject



82
83
84
85
86
87
88
89
# File 'lib/seeing_is_believing/code.rb', line 82

def line_indexes
  @line_indexes ||= [ 0,
                      *raw.each_char
                          .with_index(1)
                          .select { |char, index| char == "\n" }
                          .map    { |char, index| index },
                    ].freeze
end

#linenum_to_index(line_num) ⇒ Object



51
52
53
54
# File 'lib/seeing_is_believing/code.rb', line 51

def linenum_to_index(line_num)
  return raw.size if line_indexes.size < line_num
  line_indexes[line_num - 1]
end

#range_for(start_index, end_index) ⇒ Object



43
44
45
# File 'lib/seeing_is_believing/code.rb', line 43

def range_for(start_index, end_index)
  Parser::Source::Range.new buffer, start_index, end_index
end

#void_value?(ast) ⇒ Boolean

Returns:

  • (Boolean)


67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/seeing_is_believing/code.rb', line 67

def void_value?(ast)
  case ast && ast.type
  when :begin, :kwbegin, :resbody
    void_value?(ast.children.last)
  when :rescue, :ensure
    ast.children.any? { |child| void_value? child }
  when :if
    void_value?(ast.children[1]) || void_value?(ast.children[2])
  when :return, :next, :redo, :retry, :break
    true
  else
    false
  end
end