dead_end 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91a09272a46d3519c0ca78dcd3fd38199898fea1d6261c18576ff832e55351bd
4
- data.tar.gz: f50e76a749fe0ecdf871fdaf6aead25132cb26a5d03068cbc611476c6eb06a8c
3
+ metadata.gz: 125719d74f88dcc5d196a1570508bd36d83d7ece6058b029a579da90a70cc099
4
+ data.tar.gz: 2eea6be22997101b46ff2820dfabaef4e473424a552e7f9c2835e43471c4431b
5
5
  SHA512:
6
- metadata.gz: 89e7c7734a7a74c735479d35418ffda7d53557ce7d0c8a47fb2a67f5581d87d1be8117ff62d6590ed2cbf633fb566b797a6f3c3033e6c6a50b543cfbba9f4718
7
- data.tar.gz: 2804e68394e928f36803859eb0e193bf96dc088138c4fea87510d6bc0d70e9c5485b6ca4c402762174c1ba3b139b9a4d1af28a6b31e87ec4e36859f659afc42d
6
+ metadata.gz: 674e35c603ec9ed2a8662e6ee3472381c6108a8734f455d39ef30472e38d13fd08a23cb107333f9ff8353ace614cb6c1f5bcb21e6aeb5384911ad1a61eb1ff58
7
+ data.tar.gz: 4ed5351764e96b5c95b2c4f68403bd81a3739163ad83d9e473829a1f6571f7e3614b855f56f21f5e8f15434817f7b7e8a947146044d4ac4c4f9314a15cd6b9bf
@@ -1,5 +1,10 @@
1
1
  ## HEAD (unreleased)
2
2
 
3
+ ## 1.0.2
4
+
5
+ - Fix bug where empty lines were interpreted to have a zero indentation (https://github.com/zombocom/dead_end/pull/39)
6
+ - Better results when missing "end" comes at the end of a capturing block (such as a class or module definition) (https://github.com/zombocom/dead_end/issues/32)
7
+
3
8
  ## 1.0.1
4
9
 
5
10
  - Fix performance issue when evaluating multiple block combinations (https://github.com/zombocom/dead_end/pull/35)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dead_end (1.0.1)
4
+ dead_end (1.0.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -163,8 +163,8 @@ module DeadEnd
163
163
 
164
164
  def scan_adjacent_indent
165
165
  before_after_indent = []
166
- before_after_indent << next_up&.indent || 0
167
- before_after_indent << next_down&.indent || 0
166
+ before_after_indent << (next_up&.indent || 0)
167
+ before_after_indent << (next_down&.indent || 0)
168
168
 
169
169
  indent = before_after_indent.min
170
170
  self.scan_while {|line| line.not_empty? && line.indent >= indent }
@@ -35,20 +35,9 @@ module DeadEnd
35
35
 
36
36
  def call
37
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
38
+ capture_last_end_same_indent(block)
39
+ capture_before_after_kws(block)
40
+ capture_falling_indent(block)
52
41
  end
53
42
 
54
43
  @lines_to_output.select!(&:not_empty?)
@@ -58,5 +47,70 @@ module DeadEnd
58
47
 
59
48
  return @lines_to_output
60
49
  end
50
+
51
+ def capture_falling_indent(block)
52
+ AroundBlockScan.new(
53
+ block: block,
54
+ code_lines: @code_lines,
55
+ ).on_falling_indent do |line|
56
+ @lines_to_output << line
57
+ end
58
+ end
59
+
60
+ def capture_before_after_kws(block)
61
+ around_lines = AroundBlockScan.new(code_lines: @code_lines, block: block)
62
+ .start_at_next_line
63
+ .capture_neighbor_context
64
+
65
+ around_lines -= block.lines
66
+
67
+ @lines_to_output.concat(around_lines)
68
+ end
69
+
70
+ # Problems heredocs are back in play
71
+ def capture_last_end_same_indent(block)
72
+ start_index = block.visible_lines.first.index
73
+ lines = @code_lines[start_index..block.lines.last.index]
74
+ kw_end_lines = lines.select {|line| line.indent == block.current_indent && (line.is_end? || line.is_kw?) }
75
+
76
+
77
+ # TODO handle case of heredocs showing up here
78
+ #
79
+ # Due to https://github.com/zombocom/dead_end/issues/32
80
+ # There's a special case where a keyword right before the last
81
+ # end of a valid block accidentally ends up identifying that the problem
82
+ # was with the block instead of before it. To handle that
83
+ # special case, we can re-parse back through the internals of blocks
84
+ # and if they have mis-matched keywords and ends show the last one
85
+ end_lines = kw_end_lines.select(&:is_end?)
86
+ end_lines.each_with_index do |end_line, i|
87
+ start_index = i.zero? ? 0 : end_lines[i-1].index
88
+ end_index = end_line.index - 1
89
+ lines = @code_lines[start_index..end_index]
90
+
91
+ stop_next = false
92
+ kw_count = 0
93
+ end_count = 0
94
+ lines = lines.reverse.take_while do |line|
95
+ next false if stop_next
96
+
97
+ end_count += 1 if line.is_end?
98
+ kw_count += 1 if line.is_kw?
99
+
100
+ stop_next = true if !kw_count.zero? && kw_count >= end_count
101
+ true
102
+ end.reverse
103
+
104
+ next unless kw_count > end_count
105
+
106
+ lines = lines.select {|line| line.is_kw? || line.is_end? }
107
+
108
+ next if lines.empty?
109
+
110
+ @lines_to_output << end_line
111
+ @lines_to_output << lines.first
112
+ @lines_to_output << lines.last
113
+ end
114
+ end
61
115
  end
62
116
  end
@@ -17,10 +17,12 @@ module DeadEnd
17
17
  #
18
18
  #
19
19
  class CodeBlock
20
+ UNSET = Object.new.freeze
20
21
  attr_reader :lines
21
22
 
22
23
  def initialize(lines: [])
23
24
  @lines = Array(lines)
25
+ @valid = UNSET
24
26
  end
25
27
 
26
28
  def visible_lines
@@ -68,7 +70,8 @@ module DeadEnd
68
70
  end
69
71
 
70
72
  def valid?
71
- DeadEnd.valid?(self.to_s)
73
+ return @valid if @valid != UNSET
74
+ @valid = DeadEnd.valid?(self.to_s)
72
75
  end
73
76
 
74
77
  def to_s
@@ -10,20 +10,20 @@ module DeadEnd
10
10
  # sorted and then the frontier can be filtered. Large blocks that totally contain a
11
11
  # smaller block will cause the smaller block to be evicted.
12
12
  #
13
- # CodeFrontier#<<
14
- # CodeFrontier#pop
13
+ # CodeFrontier#<<(block) # Adds block to frontier
14
+ # CodeFrontier#pop # Removes block from frontier
15
15
  #
16
16
  # ## Knowing where we can go
17
17
  #
18
- # Internally it keeps track of an "indent hash" which is exposed via `next_indent_line`
18
+ # Internally it keeps track of "unvisited" lines which is exposed via `next_indent_line`
19
19
  # when called this will return a line of code with the most indentation.
20
20
  #
21
- # This line of code can be used to build a CodeBlock via and then when that code block
22
- # is added back to the frontier, then the lines in the code block are removed from the
23
- # indent hash so we don't double-create the same block.
21
+ # This line of code can be used to build a CodeBlock and then when that code block
22
+ # is added back to the frontier, then the lines are removed from the
23
+ # "unvisited" so we don't double-create the same block.
24
24
  #
25
- # CodeFrontier#next_indent_line
26
- # CodeFrontier#register_indent_block
25
+ # CodeFrontier#next_indent_line # Shows next line
26
+ # CodeFrontier#register_indent_block(block) # Removes lines from unvisited
27
27
  #
28
28
  # ## Knowing when to stop
29
29
  #
@@ -42,13 +42,7 @@ module DeadEnd
42
42
  def initialize(code_lines: )
43
43
  @code_lines = code_lines
44
44
  @frontier = []
45
- @indent_hash = {}
46
- code_lines.each do |line|
47
- next if line.empty?
48
-
49
- @indent_hash[line.indent] ||= []
50
- @indent_hash[line.indent] << line
51
- end
45
+ @unvisited_lines = @code_lines.sort_by(&:indent_index)
52
46
  end
53
47
 
54
48
  def count
@@ -75,38 +69,31 @@ module DeadEnd
75
69
  return @frontier.pop
76
70
  end
77
71
 
78
- def indent_hash_indent
79
- @indent_hash.keys.sort.last
80
- end
81
-
82
72
  def next_indent_line
83
- indent = @indent_hash.keys.sort.last
84
- @indent_hash[indent]&.first
73
+ @unvisited_lines.last
85
74
  end
86
75
 
87
76
  def expand?
88
77
  return false if @frontier.empty?
89
- return true if @indent_hash.empty?
78
+ return true if @unvisited_lines.empty?
90
79
 
91
80
  frontier_indent = @frontier.last.current_indent
92
- hash_indent = @indent_hash.keys.sort.last
81
+ unvisited_indent= next_indent_line.indent
93
82
 
94
83
  if ENV["DEBUG"]
95
84
  puts "```"
96
85
  puts @frontier.last.to_s
97
86
  puts "```"
98
87
  puts " @frontier indent: #{frontier_indent}"
99
- puts " @hash indent: #{hash_indent}"
88
+ puts " @unvisited indent: #{unvisited_indent}"
100
89
  end
101
90
 
102
- frontier_indent >= hash_indent
91
+ # Expand all blocks before moving to unvisited lines
92
+ frontier_indent >= unvisited_indent
103
93
  end
104
94
 
105
95
  def register_indent_block(block)
106
- block.lines.each do |line|
107
- @indent_hash[line.indent]&.delete(line)
108
- end
109
- @indent_hash.select! {|k, v| !v.empty?}
96
+ @unvisited_lines -= block.lines
110
97
  self
111
98
  end
112
99
 
@@ -36,9 +36,14 @@ module DeadEnd
36
36
  def initialize(line: , index:)
37
37
  @original_line = line.freeze
38
38
  @line = @original_line
39
- @empty = line.strip.empty?
39
+ if line.strip.empty?
40
+ @empty = true
41
+ @indent = 0
42
+ else
43
+ @empty = false
44
+ @indent = SpaceCount.indent(line)
45
+ end
40
46
  @index = index
41
- @indent = SpaceCount.indent(line)
42
47
  @status = nil # valid, invalid, unknown
43
48
  @invalid = false
44
49
 
@@ -60,9 +65,10 @@ module DeadEnd
60
65
  end
61
66
  end
62
67
 
68
+ @is_comment = lex.detect {|lex| lex.type != :on_sp}&.type == :on_comment
69
+ return if @is_comment
63
70
  @is_kw = (kw_count - end_count) > 0
64
71
  @is_end = (end_count - kw_count) > 0
65
- @is_comment = lex.detect {|lex| lex.type != :on_sp}&.type == :on_comment
66
72
  @is_trailing_slash = lex.last.token == TRAILING_SLASH
67
73
  end
68
74
 
@@ -72,6 +78,10 @@ module DeadEnd
72
78
  @is_trailing_slash
73
79
  end
74
80
 
81
+ def indent_index
82
+ @indent_index ||= [indent, index]
83
+ end
84
+
75
85
  def <=>(b)
76
86
  self.index <=> b.index
77
87
  end
@@ -92,15 +102,6 @@ module DeadEnd
92
102
  @is_end
93
103
  end
94
104
 
95
- def mark_invalid
96
- @invalid = true
97
- self
98
- end
99
-
100
- def marked_invalid?
101
- @invalid
102
- end
103
-
104
105
  def mark_invisible
105
106
  @line = ""
106
107
  self
@@ -75,7 +75,7 @@ module DeadEnd
75
75
  record(block: block, name: name)
76
76
 
77
77
  if block.valid?
78
- block.lines.each(&:mark_invisible)
78
+ block.mark_invisible
79
79
  frontier << block
80
80
  else
81
81
  frontier << block
@@ -92,7 +92,7 @@ module DeadEnd
92
92
 
93
93
  # Parses the most indented lines into blocks that are marked
94
94
  # and added to the frontier
95
- def add_invalid_blocks
95
+ def visit_new_blocks
96
96
  max_indent = frontier.next_indent_line&.indent
97
97
 
98
98
  while (line = frontier.next_indent_line) && (line.indent == max_indent)
@@ -145,7 +145,7 @@ module DeadEnd
145
145
  if frontier.expand?
146
146
  expand_invalid_block
147
147
  else
148
- add_invalid_blocks
148
+ visit_new_blocks
149
149
  end
150
150
  end
151
151
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeadEnd
4
- VERSION = "1.0.1"
4
+ VERSION = "1.0.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dead_end
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
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-11 00:00:00.000000000 Z
11
+ date: 2020-12-28 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
@@ -76,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
76
  - !ruby/object:Gem::Version
77
77
  version: '0'
78
78
  requirements: []
79
- rubygems_version: 3.0.3
79
+ rubygems_version: 3.1.4
80
80
  signing_key:
81
81
  specification_version: 4
82
82
  summary: Find syntax errors in your source in a snap