Class: DeadEnd::CodeLine

Inherits:
Object
  • Object
show all
Defined in:
lib/dead_end/code_line.rb

Overview

Represents a single line of code of a given source file

This object contains metadata about the line such as amount of indentation, if it is empty or not, and lexical data, such as if it has an ‘end` or a keyword in it.

Visibility of lines can be toggled off. Marking a line as invisible indicates that it should not be used for syntax checks. It’s functionally the same as commenting it out.

Example:

line = CodeLine.from_source("def foo\n").first
line.number => 1
line.empty? # => false
line.visible? # => true
line.mark_invisible
line.visible? # => false

Constant Summary collapse

TRAILING_SLASH =
("\\" + $/).freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(line:, index:, lex:) ⇒ CodeLine

Returns a new instance of CodeLine.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/dead_end/code_line.rb', line 42

def initialize(line:, index:, lex:)
  @lex = lex
  @line = line
  @index = index
  @original = line
  @line_number = @index + 1
  strip_line = line.dup
  strip_line.lstrip!

  if strip_line.empty?
    @empty = true
    @indent = 0
  else
    @empty = false
    @indent = line.length - strip_line.length
  end

  set_kw_end
end

Instance Attribute Details

#indentObject (readonly)

Returns the value of attribute indent.



41
42
43
# File 'lib/dead_end/code_line.rb', line 41

def indent
  @indent
end

#indexObject (readonly)

Returns the value of attribute index.



41
42
43
# File 'lib/dead_end/code_line.rb', line 41

def index
  @index
end

#lexObject (readonly)

Returns the value of attribute lex.



41
42
43
# File 'lib/dead_end/code_line.rb', line 41

def lex
  @lex
end

#lineObject (readonly)

Returns the value of attribute line.



41
42
43
# File 'lib/dead_end/code_line.rb', line 41

def line
  @line
end

#line_numberObject (readonly) Also known as: number

Returns the value of attribute line_number.



41
42
43
# File 'lib/dead_end/code_line.rb', line 41

def line_number
  @line_number
end

#originalObject (readonly)

When the code line is marked invisible we retain the original value of it’s line this is useful for debugging and for showing extra context

DisplayCodeWithLineNumbers will render all lines given to it, not just visible lines, it uses the original method to obtain them.



148
149
150
# File 'lib/dead_end/code_line.rb', line 148

def original
  @original
end

Class Method Details

.from_source(source, lines: nil) ⇒ Object

Returns an array of CodeLine objects from the source string



29
30
31
32
33
34
35
36
37
38
39
# File 'lib/dead_end/code_line.rb', line 29

def self.from_source(source, lines: nil)
  lines ||= source.lines
  lex_array_for_line = LexAll.new(source: source, source_lines: lines).each_with_object(Hash.new { |h, k| h[k] = [] }) { |lex, hash| hash[lex.line] << lex }
  lines.map.with_index do |line, index|
    CodeLine.new(
      line: line,
      index: index,
      lex: lex_array_for_line[index + 1]
    )
  end
end

Instance Method Details

#<=>(other) ⇒ Object

Comparison operator, needed for equality and sorting



152
153
154
# File 'lib/dead_end/code_line.rb', line 152

def <=>(other)
  index <=> other.index
end

#empty?Boolean

An ‘empty?` line is one that was originally left empty in the source code, while a “hidden” line is one that we’ve since marked as “invisible”

Returns:

  • (Boolean)


117
118
119
# File 'lib/dead_end/code_line.rb', line 117

def empty?
  @empty
end

#hidden?Boolean

Opposite or ‘visible?` (note: different than `empty?`)

Returns:

  • (Boolean)


110
111
112
# File 'lib/dead_end/code_line.rb', line 110

def hidden?
  !visible?
end

#ignore_newline_not_beg?Boolean

Not stable API

Lines that have a ‘on_ignored_nl` type token and NOT a `BEG` type seem to be a good proxy for the ability to join multiple lines into one.

This predicate method is used to determine when those two criteria have been met.

The one known case this doesn’t handle is:

Ripper.lex <<~EOM
  a &&
   b ||
   c
EOM

For some reason this introduces ‘on_ignore_newline` but with BEG type

Returns:

  • (Boolean)


174
175
176
# File 'lib/dead_end/code_line.rb', line 174

def ignore_newline_not_beg?
  @ignore_newline_not_beg
end

#indent_indexObject

Used for stable sort via indentation level

Ruby’s sort is not “stable” meaning that when multiple elements have the same value, they are not guaranteed to return in the same order they were put in.

So when multiple code lines have the same indentation level, they’re sorted by their index value which is unique and consistent.

This is mostly needed for consistency of the test suite



74
75
76
# File 'lib/dead_end/code_line.rb', line 74

def indent_index
  @indent_index ||= [indent, index]
end

#is_end?Boolean

Returns true if the code line is determined to contain an ‘end` keyword

Returns:

  • (Boolean)


89
90
91
# File 'lib/dead_end/code_line.rb', line 89

def is_end?
  @is_end
end

#is_kw?Boolean

Returns true if the code line is determined to contain a keyword that matches with an ‘end`

For example: ‘def`, `do`, `begin`, `ensure`, etc.

Returns:

  • (Boolean)


83
84
85
# File 'lib/dead_end/code_line.rb', line 83

def is_kw?
  @is_kw
end

#mark_invisibleObject

Used to hide lines

The search alorithm will group lines into blocks then if those blocks are determined to represent valid code they will be hidden



98
99
100
# File 'lib/dead_end/code_line.rb', line 98

def mark_invisible
  @line = ""
end

#not_empty?Boolean

Opposite of ‘empty?` (note: different than `visible?`)

Returns:

  • (Boolean)


122
123
124
# File 'lib/dead_end/code_line.rb', line 122

def not_empty?
  !empty?
end

#to_sObject

Renders the given line

Also allows us to represent source code as an array of code lines.

When we have an array of code line elements calling ‘join` on the array will call `to_s` on each element, which essentially converts it back into it’s original source string.



135
136
137
# File 'lib/dead_end/code_line.rb', line 135

def to_s
  line
end

#trailing_slash?Boolean

Determines if the given line has a trailing slash

lines = CodeLine.from_source(<<~EOM)
  it "foo" \
EOM
expect(lines.first.trailing_slash?).to eq(true)

Returns:

  • (Boolean)


185
186
187
188
189
190
191
# File 'lib/dead_end/code_line.rb', line 185

def trailing_slash?
  last = @lex.last
  return false unless last
  return false unless last.type == :on_sp

  last.token == TRAILING_SLASH
end

#visible?Boolean

Means the line was marked as “invisible” Confusingly, “empty” lines are visible…they just don’t contain any source code other than a newline (“n”).

Returns:

  • (Boolean)


105
106
107
# File 'lib/dead_end/code_line.rb', line 105

def visible?
  !line.empty?
end