syntax_search 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77f054eda8b4e25dcaf0306818ae17bb1ff3da9c52f2d606141ff44cd343c51d
4
- data.tar.gz: 44f4347d5078d0250286e78a08ff730b02094aeb05897c9088963381a95e9071
3
+ metadata.gz: 66acadf6481512affd39e4a1fb691ed41a6be537a8a63801f71316863d30494d
4
+ data.tar.gz: 1bcb85706516cbe5c3f53a1ed48bf5215479672952fc62d2e3221c583e468b22
5
5
  SHA512:
6
- metadata.gz: 6ccc7e2ed620689614bdfae8943b8c15b81b4567d3b42e2c734ac7974699ed231272ce52929eff9d49eb25edd843ae3f7fa2398d0552b77665be779a4412461f
7
- data.tar.gz: 1e866b4aaafb534f946da5e00efb50536a4d20bf0e1527f8641fb298cca5cc6b7d0e1f744f63da443b9920500d7d4ddf0113ffa8bde2b4689cd6793538a12b6e
6
+ metadata.gz: 4e60777bd284d78436db8608f34d11b4b5aa8456bfe0c9507ddf3a0e3b97b902a2d00d1537433ed914c637a0729ccb253fc9047fb6db9e3b18a8cc8d0294ae06
7
+ data.tar.gz: 2fb1b35e70507f4b358be4b767cbf266c2d47c55efba1b77d7ca5f260cac4e77d519fc7439f55c78c5294c0c87240d15b4cab3721c8cfd580b518f5051b7cd0c
@@ -1,5 +1,11 @@
1
1
  ## HEAD (unreleased)
2
2
 
3
+ ## 0.2.0
4
+
5
+ - Simplify large file output so minimal context around the invalid section is shown (https://github.com/zombocom/syntax_search/pull/26)
6
+ - Block expansion is now lexically aware of keywords (def/do/end etc.) (https://github.com/zombocom/syntax_search/pull/24)
7
+ - Fix bug where not all of a source is lexed which is used in heredoc detection/removal (https://github.com/zombocom/syntax_search/pull/23)
8
+
3
9
  ## 0.1.5
4
10
 
5
11
  - Strip out heredocs in documents first (https://github.com/zombocom/syntax_search/pull/19)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- syntax_search (0.1.5)
4
+ syntax_search (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -62,6 +62,7 @@ if file.nil? || file.empty?
62
62
  end
63
63
 
64
64
  file = Pathname(file)
65
+ options[:record_dir] = "tmp" if ENV["DEBUG"]
65
66
 
66
67
  $stderr.puts "Record dir: #{options[:record_dir]}" if options[:record_dir]
67
68
 
@@ -142,3 +142,4 @@ require_relative "syntax_search/parse_blocks_from_indent_line"
142
142
  require_relative "syntax_search/code_search"
143
143
  require_relative "syntax_search/who_dis_syntax_error"
144
144
  require_relative "syntax_search/heredoc_block_parse"
145
+ require_relative "syntax_search/lex_all"
@@ -32,9 +32,11 @@ module SyntaxErrorSearch
32
32
  @code_lines = code_lines
33
33
  @orig_before_index = block.lines.first.index
34
34
  @orig_after_index = block.lines.last.index
35
+ @orig_indent = block.current_indent
35
36
  @skip_array = []
36
37
  @after_array = []
37
38
  @before_array = []
39
+ @stop_after_kw = false
38
40
  end
39
41
 
40
42
  def skip(name)
@@ -42,29 +44,129 @@ module SyntaxErrorSearch
42
44
  self
43
45
  end
44
46
 
47
+ def stop_after_kw
48
+ @stop_after_kw = true
49
+ self
50
+ end
51
+
45
52
  def scan_while(&block)
53
+ stop_next = false
54
+
55
+ kw_count = 0
56
+ end_count = 0
46
57
  @before_index = before_lines.reverse_each.take_while do |line|
58
+ next false if stop_next
47
59
  next true if @skip_array.detect {|meth| line.send(meth) }
48
60
 
61
+ kw_count += 1 if line.is_kw?
62
+ end_count += 1 if line.is_end?
63
+ if @stop_after_kw && kw_count > end_count
64
+ stop_next = true
65
+ end
66
+
49
67
  block.call(line)
50
68
  end.reverse.first&.index
51
69
 
70
+ stop_next = false
71
+ kw_count = 0
72
+ end_count = 0
52
73
  @after_index = after_lines.take_while do |line|
74
+ next false if stop_next
53
75
  next true if @skip_array.detect {|meth| line.send(meth) }
54
76
 
77
+ kw_count += 1 if line.is_kw?
78
+ end_count += 1 if line.is_end?
79
+ if @stop_after_kw && end_count > kw_count
80
+ stop_next = true
81
+ end
82
+
55
83
  block.call(line)
56
84
  end.last&.index
57
85
  self
58
86
  end
59
87
 
88
+ def capture_neighbor_context
89
+ lines = []
90
+ kw_count = 0
91
+ end_count = 0
92
+ before_lines.reverse.each do |line|
93
+ next if line.empty?
94
+ break if line.indent < @orig_indent
95
+ next if line.indent != @orig_indent
96
+
97
+ kw_count += 1 if line.is_kw?
98
+ end_count += 1 if line.is_end?
99
+ if kw_count != 0 && kw_count == end_count
100
+ lines << line
101
+ break
102
+ end
103
+
104
+ lines << line
105
+ end
106
+
107
+ lines.reverse!
108
+
109
+ kw_count = 0
110
+ end_count = 0
111
+ after_lines.each do |line|
112
+ # puts "line: #{line.number} #{line.original_line}, indent: #{line.indent}, #{line.empty?} #{line.indent == @orig_indent}"
113
+
114
+ next if line.empty?
115
+ break if line.indent < @orig_indent
116
+ next if line.indent != @orig_indent
117
+
118
+ kw_count += 1 if line.is_kw?
119
+ end_count += 1 if line.is_end?
120
+ if kw_count != 0 && kw_count == end_count
121
+ lines << line
122
+ break
123
+ end
124
+
125
+ lines << line
126
+ end
127
+ lines.select! {|line| !line.is_comment? }
128
+
129
+ lines
130
+ end
131
+
132
+ def on_falling_indent
133
+ last_indent = @orig_indent
134
+ before_lines.reverse.each do |line|
135
+ next if line.empty?
136
+ if line.indent < last_indent
137
+ yield line
138
+ last_indent = line.indent
139
+ end
140
+ end
141
+
142
+ last_indent = @orig_indent
143
+ after_lines.each do |line|
144
+ next if line.empty?
145
+ if line.indent < last_indent
146
+ yield line
147
+ last_indent = line.indent
148
+ end
149
+ end
150
+ end
151
+
152
+ def scan_neighbors
153
+ self.scan_while {|line| line.not_empty? && line.indent >= @orig_indent }
154
+ end
155
+
60
156
  def scan_adjacent_indent
61
157
  before_indent = @code_lines[@orig_before_index.pred]&.indent || 0
62
158
  after_indent = @code_lines[@orig_after_index.next]&.indent || 0
63
159
 
64
160
  indent = [before_indent, after_indent].min
65
- @before_index = before_index.pred if before_indent >= indent
66
- @after_index = after_index.next if after_indent >= indent
161
+ self.scan_while {|line| line.not_empty? && line.indent >= indent }
162
+
163
+ self
164
+ end
67
165
 
166
+ def start_at_next_line
167
+ before_index; after_index
168
+ @before_index -= 1
169
+ @after_index += 1
68
170
  self
69
171
  end
70
172
 
@@ -73,11 +175,11 @@ module SyntaxErrorSearch
73
175
  end
74
176
 
75
177
  def before_index
76
- @before_index || @orig_before_index
178
+ @before_index ||= @orig_before_index
77
179
  end
78
180
 
79
181
  def after_index
80
- @after_index || @orig_after_index
182
+ @after_index ||= @orig_after_index
81
183
  end
82
184
 
83
185
  private def before_lines
@@ -44,21 +44,17 @@ module SyntaxErrorSearch
44
44
 
45
45
  def expand_indent(block)
46
46
  block = AroundBlockScan.new(code_lines: @code_lines, block: block)
47
+ .skip(:hidden?)
48
+ .stop_after_kw
47
49
  .scan_adjacent_indent
48
50
  .code_block
49
-
50
- # Handle if/else/end case
51
- if (next_block = expand_neighbors(block, grab_empty: false))
52
- return next_block
53
- else
54
- return block
55
- end
56
51
  end
57
52
 
58
53
  def expand_neighbors(block, grab_empty: true)
59
54
  scan = AroundBlockScan.new(code_lines: @code_lines, block: block)
60
55
  .skip(:hidden?)
61
- .scan_while {|line| line.not_empty? && line.indent >= block.current_indent }
56
+ .stop_after_kw
57
+ .scan_neighbors
62
58
 
63
59
  # Slurp up empties
64
60
  if grab_empty
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxErrorSearch
4
+
5
+ # Given a block, this method will capture surrounding
6
+ # code to give the user more context for the location of
7
+ # the problem.
8
+ #
9
+ # Return is an array of CodeLines to be rendered.
10
+ #
11
+ # Surrounding code is captured regardless of visible state
12
+ #
13
+ # puts block.to_s # => "def bark"
14
+ #
15
+ # context = CaptureCodeContext.new(
16
+ # blocks: block,
17
+ # code_lines: code_lines
18
+ # )
19
+ #
20
+ # puts context.call.join
21
+ # # =>
22
+ # class Dog
23
+ # def bark
24
+ # end
25
+ #
26
+ class CaptureCodeContext
27
+ attr_reader :code_lines
28
+
29
+ def initialize(blocks: , code_lines:)
30
+ @blocks = Array(blocks)
31
+ @code_lines = code_lines
32
+ @visible_lines = @blocks.map(&:visible_lines).flatten
33
+ @lines_to_output = @visible_lines.dup
34
+ end
35
+
36
+ def call
37
+ @blocks.each do |block|
38
+ around_lines = AroundBlockScan.new(code_lines: @code_lines, block: block)
39
+ .start_at_next_line
40
+ .capture_neighbor_context
41
+
42
+ around_lines -= block.lines
43
+
44
+ @lines_to_output.concat(around_lines)
45
+
46
+ AroundBlockScan.new(
47
+ block: block,
48
+ code_lines: @code_lines,
49
+ ).on_falling_indent do |line|
50
+ @lines_to_output << line
51
+ end
52
+ end
53
+
54
+ @lines_to_output.select!(&:not_empty?)
55
+ @lines_to_output.select!(&:not_comment?)
56
+ @lines_to_output.uniq!
57
+ @lines_to_output.sort!
58
+
59
+ return @lines_to_output
60
+ end
61
+ end
62
+ end
@@ -23,6 +23,10 @@ module SyntaxErrorSearch
23
23
  @lines = Array(lines)
24
24
  end
25
25
 
26
+ def visible_lines
27
+ @lines.select(&:visible?).select(&:not_empty?)
28
+ end
29
+
26
30
  def mark_invisible
27
31
  @lines.map(&:mark_invisible)
28
32
  end
@@ -48,7 +52,11 @@ module SyntaxErrorSearch
48
52
  # populate an array with multiple code blocks then call `sort!`
49
53
  # on it without having to specify the sorting criteria
50
54
  def <=>(other)
51
- self.current_indent <=> other.current_indent
55
+ out = self.current_indent <=> other.current_indent
56
+ return out if out != 0
57
+
58
+ # Stable sort
59
+ self.starts_at <=> other.starts_at
52
60
  end
53
61
 
54
62
  def current_indent
@@ -29,7 +29,7 @@ module SyntaxErrorSearch
29
29
  # Marking a line as invisible also lets the overall program know
30
30
  # that it should not check that area for syntax errors.
31
31
  class CodeLine
32
- attr_reader :line, :index, :indent
32
+ attr_reader :line, :index, :indent, :original_line
33
33
 
34
34
  def initialize(line: , index:)
35
35
  @original_line = line.freeze
@@ -39,6 +39,45 @@ module SyntaxErrorSearch
39
39
  @indent = SpaceCount.indent(line)
40
40
  @status = nil # valid, invalid, unknown
41
41
  @invalid = false
42
+
43
+ @kw_count = 0
44
+ @end_count = 0
45
+ @lex = LexAll.new(source: line)
46
+ @lex.each do |lex|
47
+ next unless lex.type == :on_kw
48
+
49
+ case lex.token
50
+ when 'def', 'case', 'for', 'begin', 'class', 'module', 'if', 'unless', 'while', 'until' , 'do'
51
+ @kw_count += 1
52
+ when 'end'
53
+ @end_count += 1
54
+ end
55
+ end
56
+
57
+ @is_comment = true if @lex.detect {|lex| lex.type != :on_sp}&.type == :on_comment
58
+
59
+ @is_kw = (@kw_count - @end_count) > 0
60
+ @is_end = (@end_count - @kw_count) > 0
61
+ end
62
+
63
+ def <=>(b)
64
+ self.index <=> b.index
65
+ end
66
+
67
+ def is_comment?
68
+ @is_comment
69
+ end
70
+
71
+ def not_comment?
72
+ !is_comment?
73
+ end
74
+
75
+ def is_kw?
76
+ @is_kw
77
+ end
78
+
79
+ def is_end?
80
+ @is_end
42
81
  end
43
82
 
44
83
  def mark_invalid
@@ -72,6 +111,8 @@ module SyntaxErrorSearch
72
111
  index + 1
73
112
  end
74
113
 
114
+ alias :number :line_number
115
+
75
116
  def not_empty?
76
117
  !empty?
77
118
  end
@@ -28,7 +28,7 @@ module SyntaxErrorSearch
28
28
  private; attr_reader :frontier; public
29
29
  public; attr_reader :invalid_blocks, :record_dir, :code_lines
30
30
 
31
- def initialize(source, record_dir: ENV["SYNTAX_SEARCH_RECORD_DIR"])
31
+ def initialize(source, record_dir: ENV["SYNTAX_SEARCH_RECORD_DIR"] || ENV["DEBUG"] ? "tmp" : nil)
32
32
  @source = source
33
33
  if record_dir
34
34
  @time = Time.now.strftime('%Y-%m-%d-%H-%M-%s-%N')
@@ -79,6 +79,14 @@ module SyntaxErrorSearch
79
79
  end
80
80
  end
81
81
 
82
+ # Removes the block without putting it back in the frontier
83
+ def sweep(block:, name: )
84
+ record(block: block, name: name)
85
+
86
+ block.lines.each(&:mark_invisible)
87
+ frontier.register_indent_block(block)
88
+ end
89
+
82
90
  # Parses the most indented lines into blocks that are marked
83
91
  # and added to the frontier
84
92
  def add_invalid_blocks
@@ -108,7 +116,6 @@ module SyntaxErrorSearch
108
116
  push(block, name: "expand")
109
117
  end
110
118
 
111
-
112
119
  def sweep_heredocs
113
120
  HeredocBlockParse.new(
114
121
  source: @source,
@@ -118,9 +125,17 @@ module SyntaxErrorSearch
118
125
  end
119
126
  end
120
127
 
128
+ def sweep_comments
129
+ lines = @code_lines.select(&:is_comment?)
130
+ return if lines.empty?
131
+ block = CodeBlock.new(lines: lines)
132
+ sweep(block: block, name: "comments")
133
+ end
134
+
121
135
  # Main search loop
122
136
  def call
123
137
  sweep_heredocs
138
+ sweep_comments
124
139
  until frontier.holds_all_syntax_errors?
125
140
  @tick += 1
126
141
 
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxErrorSearch
4
+ # Outputs code with highlighted lines
5
+ #
6
+ # Whatever is passed to this class will be rendered
7
+ # even if it is "marked invisible" any filtering of
8
+ # output should be done before calling this class.
9
+ #
10
+ #
11
+ # DisplayCodeWithLineNumbers.new(
12
+ # lines: lines,
13
+ # highlight_lines: [lines[2], lines[3]]
14
+ # ).call
15
+ # # =>
16
+ # 1
17
+ # 2 def cat
18
+ # ❯ 3 Dir.chdir
19
+ # ❯ 4 end
20
+ # 5 end
21
+ # 6
22
+ class DisplayCodeWithLineNumbers
23
+ TERMINAL_HIGHLIGHT = "\e[1;3m" # Bold, italics
24
+ TERMINAL_END = "\e[0m"
25
+
26
+ def initialize(lines: , highlight_lines: [], terminal: false)
27
+ @lines = lines.sort
28
+ @terminal = terminal
29
+ @highlight_line_hash = highlight_lines.each_with_object({}) {|line, h| h[line] = true }
30
+ @digit_count = @lines.last&.line_number.to_s.length
31
+ end
32
+
33
+ def call
34
+ @lines.map do |line|
35
+ string = String.new("")
36
+ if @highlight_line_hash[line]
37
+ string << "❯ "
38
+ else
39
+ string << " "
40
+ end
41
+
42
+ number = line.line_number.to_s.rjust(@digit_count)
43
+ string << number.to_s
44
+ if line.empty?
45
+ string << line.original_line
46
+ else
47
+ string << " "
48
+ string << TERMINAL_HIGHLIGHT if @terminal && @highlight_line_hash[line] # Bold, italics
49
+ string << line.original_line
50
+ string << TERMINAL_END if @terminal
51
+ end
52
+ string
53
+ end.join
54
+ end
55
+ end
56
+ end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "capture_code_context"
4
+ require_relative "display_code_with_line_numbers"
5
+
3
6
  module SyntaxErrorSearch
4
7
  # Used for formatting invalid blocks
5
8
  class DisplayInvalidBlocks
@@ -11,11 +14,10 @@ module SyntaxErrorSearch
11
14
  @io = io
12
15
 
13
16
  @blocks = Array(blocks)
14
- @lines = @blocks.map(&:lines).flatten
17
+
18
+ @invalid_lines = @blocks.map(&:lines).flatten
15
19
  @code_lines = code_lines
16
- @digit_count = @code_lines.last&.line_number.to_s.length
17
20
 
18
- @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true }
19
21
  @invalid_type = invalid_type
20
22
  end
21
23
 
@@ -70,41 +72,29 @@ module SyntaxErrorSearch
70
72
 
71
73
  def code_block
72
74
  string = String.new("")
73
- string << code_with_lines
75
+ string << code_with_context
74
76
  string
75
77
  end
76
78
 
77
- def terminal_end
78
- "\e[0m"
79
- end
80
-
81
- def terminal_highlight
82
- "\e[1;3m" # Bold, italics
79
+ def code_with_context
80
+ lines = CaptureCodeContext.new(
81
+ blocks: @blocks,
82
+ code_lines: @code_lines
83
+ ).call
84
+
85
+ DisplayCodeWithLineNumbers.new(
86
+ lines: lines,
87
+ terminal: @terminal,
88
+ highlight_lines: @invalid_lines,
89
+ ).call
83
90
  end
84
91
 
85
92
  def code_with_lines
86
- @code_lines.map do |line|
87
- next if line.hidden?
88
-
89
- string = String.new("")
90
- if @invalid_line_hash[line]
91
- string << "❯ "
92
- else
93
- string << " "
94
- end
95
-
96
- number = line.line_number.to_s.rjust(@digit_count)
97
- string << number.to_s
98
- if line.empty?
99
- string << line.to_s
100
- else
101
- string << " "
102
- string << terminal_highlight if @terminal && @invalid_line_hash[line] # Bold, italics
103
- string << line.to_s
104
- string << terminal_end if @terminal
105
- end
106
- string
107
- end.join
93
+ DisplayCodeWithLineNumbers.new(
94
+ lines: @code_lines.select(&:visible?),
95
+ terminal: @terminal,
96
+ highlight_lines: @invalid_lines,
97
+ ).call
108
98
  end
109
99
  end
110
100
  end
@@ -7,19 +7,19 @@ module SyntaxErrorSearch
7
7
 
8
8
  def initialize(source:, code_lines: )
9
9
  @code_lines = code_lines
10
- @lex = Ripper.lex(source)
10
+ @lex = LexAll.new(source: source)
11
11
  end
12
12
 
13
13
  def call
14
14
  blocks = []
15
15
  beginning = []
16
- @lex.each do |(line, col), event, *_|
17
- case event
16
+ @lex.each do |lex|
17
+ case lex.type
18
18
  when :on_heredoc_beg
19
- beginning << line
19
+ beginning << lex.line
20
20
  when :on_heredoc_end
21
21
  start_index = beginning.pop - 1
22
- end_index = line - 1
22
+ end_index = lex.line - 1
23
23
  blocks << CodeBlock.new(lines: code_lines[start_index..end_index])
24
24
  end
25
25
  end
@@ -0,0 +1,58 @@
1
+ module SyntaxErrorSearch
2
+ # Ripper.lex is not guaranteed to lex the entire source document
3
+ #
4
+ # lex = LexAll.new(source: source)
5
+ # lex.each do |value|
6
+ # puts value.line
7
+ # end
8
+ class LexAll
9
+ include Enumerable
10
+
11
+ def initialize(source: )
12
+ @lex = Ripper.lex(source)
13
+ lineno = @lex.last&.first&.first + 1
14
+ source_lines = source.lines
15
+ last_lineno = source_lines.count
16
+
17
+ until lineno >= last_lineno
18
+ lines = source_lines[lineno..-1]
19
+
20
+ @lex.concat(Ripper.lex(lines.join, '-', lineno + 1))
21
+ lineno = @lex.last&.first&.first + 1
22
+ end
23
+
24
+ @lex.map! {|(line, _), type, token| LexValue.new(line, _, type, token) }
25
+ end
26
+
27
+ def each
28
+ return @lex.each unless block_given?
29
+ @lex.each do |x|
30
+ yield x
31
+ end
32
+ end
33
+
34
+ def last
35
+ @lex.last
36
+ end
37
+
38
+ # Value object for accessing lex values
39
+ #
40
+ # This lex:
41
+ #
42
+ # [1, 0], :on_ident, "describe", CMDARG
43
+ #
44
+ # Would translate into:
45
+ #
46
+ # lex.line # => 1
47
+ # lex.type # => :on_indent
48
+ # lex.token # => "describe"
49
+ class LexValue
50
+ attr_reader :line, :type, :token
51
+ def initialize(line, _, type, token)
52
+ @line = line
53
+ @type = type
54
+ @token = token
55
+ end
56
+ end
57
+ end
58
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SyntaxErrorSearch
4
- VERSION = "0.1.5"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: syntax_search
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - schneems
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-02 00:00:00.000000000 Z
11
+ date: 2020-12-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: When you get an "unexpected end" in your syntax this gem helps you find
14
14
  it
@@ -39,13 +39,16 @@ files:
39
39
  - lib/syntax_search/around_block_scan.rb
40
40
  - lib/syntax_search/auto.rb
41
41
  - lib/syntax_search/block_expand.rb
42
+ - lib/syntax_search/capture_code_context.rb
42
43
  - lib/syntax_search/code_block.rb
43
44
  - lib/syntax_search/code_frontier.rb
44
45
  - lib/syntax_search/code_line.rb
45
46
  - lib/syntax_search/code_search.rb
47
+ - lib/syntax_search/display_code_with_line_numbers.rb
46
48
  - lib/syntax_search/display_invalid_blocks.rb
47
49
  - lib/syntax_search/fyi.rb
48
50
  - lib/syntax_search/heredoc_block_parse.rb
51
+ - lib/syntax_search/lex_all.rb
49
52
  - lib/syntax_search/parse_blocks_from_indent_line.rb
50
53
  - lib/syntax_search/version.rb
51
54
  - lib/syntax_search/who_dis_syntax_error.rb