dead_end 1.1.4 → 1.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: d1e8a394d94d7d8f70138a9d80d7e660cca8dc06e3d3a5eb9b74334585f284aa
4
- data.tar.gz: 75296d7faaf4b117ad45bed40252566ee08752b622b2d90e4afa3be8b4a9b774
3
+ metadata.gz: c7db29aed59a901a58a0b1ed50873b6f2c17692f7724ffad2736b99694b78ba0
4
+ data.tar.gz: e1cf2a11fa38af85df30d89a559b0c6e5a74beec2c0add8b6f67dd142d793de1
5
5
  SHA512:
6
- metadata.gz: f970086da7e3f662d2dabda9a59d89af73d503dc986a9d469d45b83f25184590892e5bdb0a4c174318b2d1bb201f112c81a0a281d0a530feb49dd7f0eb1fb144
7
- data.tar.gz: cc85e81a1d996320128d7656c0cea0727f9672060d7d2747e30e77d2aeb59d3549f4dd230835c5042988c53b1252f975c472774eb2ef7f258caebea5cd5eee40
6
+ metadata.gz: 444cfdfd7df93038d1714729a21a3b7a20f9000e9cf0541521d51a125d00be4968e83bbc8a51bf4c4689e2b99706cf5eb0032a318647ecb3fd4452a343798e7a
7
+ data.tar.gz: c532b87160ae6231776b72ff68f3c8f940064051e14c27da28fa22a15c1be0060d9b2afd600bb66dd4571cd39b9372b1956782565040a5e159776f9dac9f9e62
data/.circleci/config.yml CHANGED
@@ -7,15 +7,12 @@ references:
7
7
  name: Run test suite
8
8
  command: bundle exec rspec spec/
9
9
 
10
- jobs:
11
- "ruby-2-5":
12
- docker:
13
- - image: circleci/ruby:2.5
14
- steps:
15
- - checkout
16
- - ruby/install-deps
17
- - <<: *unit
10
+ lint: &lint
11
+ run:
12
+ name: Run linter, fix with `standardrb --fix` locally
13
+ command: bundle exec standardrb
18
14
 
15
+ jobs:
19
16
  "ruby-2-6":
20
17
  docker:
21
18
  - image: circleci/ruby:2.6
@@ -40,11 +37,19 @@ jobs:
40
37
  - ruby/install-deps
41
38
  - <<: *unit
42
39
 
40
+ "lint":
41
+ docker:
42
+ - image: circleci/ruby:3.0
43
+ steps:
44
+ - checkout
45
+ - ruby/install-deps
46
+ - <<: *lint
47
+
43
48
  workflows:
44
49
  version: 2
45
50
  build:
46
51
  jobs:
47
- - "ruby-2-5"
48
52
  - "ruby-2-6"
49
53
  - "ruby-2-7"
50
54
  - "ruby-3-0"
55
+ - "lint"
data/.standard.yml ADDED
@@ -0,0 +1 @@
1
+ ruby_version: 2.6.6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  ## HEAD (unreleased)
2
2
 
3
+ ## 1.2.0
4
+
5
+ - Output improvements via less greedy unmatched kw capture https://github.com/zombocom/dead_end/pull/73
6
+ - Remove NoMethodError patching instead use https://github.com/ruby/error_highlight/ (https://github.com/zombocom/dead_end/pull/71)
7
+
8
+ ## 1.1.7
9
+
10
+ - Fix sinatra support for `require_relative` (https://github.com/zombocom/dead_end/pull/63)
11
+
12
+ ## 1.1.6
13
+
14
+ - Consider if syntax error caused an unexpected variable instead of end (https://github.com/zombocom/dead_end/pull/58)
15
+
16
+ ## 1.1.5
17
+
18
+ - Parse error once and not twice if there's more than one available (https://github.com/zombocom/dead_end/pull/57)
19
+
3
20
  ## 1.1.4
4
21
 
5
22
  - Avoid including demo gif in built gem (https://github.com/zombocom/dead_end/pull/53)
data/Gemfile CHANGED
@@ -8,3 +8,4 @@ gemspec
8
8
  gem "rake", "~> 12.0"
9
9
  gem "rspec", "~> 3.0"
10
10
  gem "stackprof"
11
+ gem "standard"
data/Gemfile.lock CHANGED
@@ -1,13 +1,20 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dead_end (1.1.4)
4
+ dead_end (1.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
+ ast (2.4.2)
9
10
  diff-lcs (1.4.4)
11
+ parallel (1.21.0)
12
+ parser (3.0.2.0)
13
+ ast (~> 2.4.1)
14
+ rainbow (3.0.0)
10
15
  rake (12.3.3)
16
+ regexp_parser (2.1.1)
17
+ rexml (3.2.5)
11
18
  rspec (3.10.0)
12
19
  rspec-core (~> 3.10.0)
13
20
  rspec-expectations (~> 3.10.0)
@@ -21,7 +28,26 @@ GEM
21
28
  diff-lcs (>= 1.2.0, < 2.0)
22
29
  rspec-support (~> 3.10.0)
23
30
  rspec-support (3.10.0)
31
+ rubocop (1.20.0)
32
+ parallel (~> 1.10)
33
+ parser (>= 3.0.0.0)
34
+ rainbow (>= 2.2.2, < 4.0)
35
+ regexp_parser (>= 1.8, < 3.0)
36
+ rexml
37
+ rubocop-ast (>= 1.9.1, < 2.0)
38
+ ruby-progressbar (~> 1.7)
39
+ unicode-display_width (>= 1.4.0, < 3.0)
40
+ rubocop-ast (1.12.0)
41
+ parser (>= 3.0.1.1)
42
+ rubocop-performance (1.11.5)
43
+ rubocop (>= 1.7.0, < 2.0)
44
+ rubocop-ast (>= 0.4.0)
45
+ ruby-progressbar (1.11.0)
24
46
  stackprof (0.2.16)
47
+ standard (1.3.0)
48
+ rubocop (= 1.20.0)
49
+ rubocop-performance (= 1.11.5)
50
+ unicode-display_width (2.1.0)
25
51
 
26
52
  PLATFORMS
27
53
  ruby
@@ -31,6 +57,7 @@ DEPENDENCIES
31
57
  rake (~> 12.0)
32
58
  rspec (~> 3.0)
33
59
  stackprof
60
+ standard
34
61
 
35
62
  BUNDLED WITH
36
- 2.2.3
63
+ 2.2.27
data/README.md CHANGED
@@ -81,27 +81,9 @@ end
81
81
 
82
82
  As well as unmatched `|` and unmatched `}`. These errors can be time consuming to debug because Ruby often only tells you the last line in the file. The command `ruby -wc path/to/file.rb` can narrow it down a little bit, but this library does a better job.
83
83
 
84
- ## What other errors does it handle?
85
-
86
- In addition to syntax errors, the NoMethodError is annotated to show the line where the error occured, and the surrounding context:
87
-
88
- ```
89
- scratch.rb:7:in `call': undefined method `upcase' for nil:NilClass (NoMethodError)
90
-
91
-
92
- 1 class Pet
93
- 6 def call
94
- ❯ 7 puts "Come here #{@neam.upcase}"
95
- 8 end
96
- 9 end
97
- ```
98
-
99
84
  ## Sounds cool, but why isn't this baked into Ruby directly?
100
85
 
101
- I would love to get something like this directly in Ruby, but I first need to prove it's useful. The `did_you_mean` functionality started as a gem that was eventually adopted by a bunch of people and then Ruby core liked it enough that they included it in the source. The goal of this gem is to:
102
-
103
- 1. Get real world useage and feedback. If we gave you an awful suggestion, let us know! We try to handle lots of cases well, but maybe we could be better.
104
- 2. Prove out demand. If you like this idea, then vote for it by putting it in your Gemfile.
86
+ We are now talking about it https://bugs.ruby-lang.org/issues/18159#change-93682.
105
87
 
106
88
  ## Artificial Inteligence?
107
89
 
data/Rakefile CHANGED
@@ -5,4 +5,4 @@ require "rspec/core/rake_task"
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
- task :default => :spec
8
+ task default: :spec
data/dead_end.gemspec CHANGED
@@ -1,17 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'lib/dead_end/version'
3
+ require_relative "lib/dead_end/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = "dead_end"
7
- spec.version = DeadEnd::VERSION
8
- spec.authors = ["schneems"]
9
- spec.email = ["richard.schneeman+foo@gmail.com"]
6
+ spec.name = "dead_end"
7
+ spec.version = DeadEnd::VERSION
8
+ spec.authors = ["schneems"]
9
+ spec.email = ["richard.schneeman+foo@gmail.com"]
10
10
 
11
- spec.summary = %q{Find syntax errors in your source in a snap}
12
- spec.description = %q{When you get an "unexpected end" in your syntax this gem helps you find it}
13
- spec.homepage = "https://github.com/zombocom/dead_end.git"
14
- spec.license = "MIT"
11
+ spec.summary = "Find syntax errors in your source in a snap"
12
+ spec.description = 'When you get an "unexpected end" in your syntax this gem helps you find it'
13
+ spec.homepage = "https://github.com/zombocom/dead_end.git"
14
+ spec.license = "MIT"
15
15
  spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
16
16
 
17
17
  spec.metadata["homepage_uri"] = spec.homepage
@@ -19,10 +19,10 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  # Specify which files should be added to the gem when it is released.
21
21
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
23
23
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|assets)/}) }
24
24
  end
25
- spec.bindir = "exe"
26
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
27
  spec.require_paths = ["lib"]
28
28
  end
data/exe/dead_end CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'pathname'
3
+ require "pathname"
4
4
  require "optparse"
5
- require_relative "../lib/dead_end.rb"
5
+ require_relative "../lib/dead_end"
6
6
 
7
7
  options = {}
8
8
  options[:terminal] = true
@@ -60,7 +60,7 @@ end
60
60
  file = Pathname(file)
61
61
  options[:record_dir] = "tmp" if ENV["DEBUG"]
62
62
 
63
- $stderr.puts "Record dir: #{options[:record_dir]}" if options[:record_dir]
63
+ warn "Record dir: #{options[:record_dir]}" if options[:record_dir]
64
64
 
65
65
  DeadEnd.call(
66
66
  source: file.read,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- #
2
+
3
3
  module DeadEnd
4
4
  # This class is useful for exploring contents before and after
5
5
  # a block
@@ -28,7 +28,7 @@ module DeadEnd
28
28
  #
29
29
  # To grab the next surrounding indentation use AroundBlockScan#scan_adjacent_indent
30
30
  class AroundBlockScan
31
- def initialize(code_lines: , block:)
31
+ def initialize(code_lines:, block:)
32
32
  @code_lines = code_lines
33
33
  @orig_before_index = block.lines.first.index
34
34
  @orig_after_index = block.lines.last.index
@@ -56,7 +56,7 @@ module DeadEnd
56
56
  end_count = 0
57
57
  @before_index = before_lines.reverse_each.take_while do |line|
58
58
  next false if stop_next
59
- next true if @skip_array.detect {|meth| line.send(meth) }
59
+ next true if @skip_array.detect { |meth| line.send(meth) }
60
60
 
61
61
  kw_count += 1 if line.is_kw?
62
62
  end_count += 1 if line.is_end?
@@ -65,14 +65,14 @@ module DeadEnd
65
65
  end
66
66
 
67
67
  block.call(line)
68
- end.reverse.first&.index
68
+ end.last&.index
69
69
 
70
70
  stop_next = false
71
71
  kw_count = 0
72
72
  end_count = 0
73
73
  @after_index = after_lines.take_while do |line|
74
74
  next false if stop_next
75
- next true if @skip_array.detect {|meth| line.send(meth) }
75
+ next true if @skip_array.detect { |meth| line.send(meth) }
76
76
 
77
77
  kw_count += 1 if line.is_kw?
78
78
  end_count += 1 if line.is_end?
@@ -89,7 +89,7 @@ module DeadEnd
89
89
  lines = []
90
90
  kw_count = 0
91
91
  end_count = 0
92
- before_lines.reverse.each do |line|
92
+ before_lines.reverse_each do |line|
93
93
  next if line.empty?
94
94
  break if line.indent < @orig_indent
95
95
  next if line.indent != @orig_indent
@@ -124,14 +124,14 @@ module DeadEnd
124
124
 
125
125
  lines << line
126
126
  end
127
- lines.select! {|line| !line.is_comment? }
127
+ lines.select! { |line| !line.is_comment? }
128
128
 
129
129
  lines
130
130
  end
131
131
 
132
132
  def on_falling_indent
133
133
  last_indent = @orig_indent
134
- before_lines.reverse.each do |line|
134
+ before_lines.reverse_each do |line|
135
135
  next if line.empty?
136
136
  if line.indent < last_indent
137
137
  yield line
@@ -150,7 +150,7 @@ module DeadEnd
150
150
  end
151
151
 
152
152
  def scan_neighbors
153
- self.scan_while {|line| line.not_empty? && line.indent >= @orig_indent }
153
+ scan_while { |line| line.not_empty? && line.indent >= @orig_indent }
154
154
  end
155
155
 
156
156
  def next_up
@@ -167,13 +167,14 @@ module DeadEnd
167
167
  before_after_indent << (next_down&.indent || 0)
168
168
 
169
169
  indent = before_after_indent.min
170
- self.scan_while {|line| line.not_empty? && line.indent >= indent }
170
+ scan_while { |line| line.not_empty? && line.indent >= indent }
171
171
 
172
172
  self
173
173
  end
174
174
 
175
175
  def start_at_next_line
176
- before_index; after_index
176
+ before_index
177
+ after_index
177
178
  @before_index -= 1
178
179
  @after_index += 1
179
180
  self
@@ -196,7 +197,7 @@ module DeadEnd
196
197
  end
197
198
 
198
199
  private def after_lines
199
- @code_lines[after_index.next..-1] || []
200
+ @code_lines[after_index.next..] || []
200
201
  end
201
202
  end
202
203
  end
data/lib/dead_end/auto.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- #
2
+
3
3
  require_relative "../dead_end/internals"
4
4
 
5
5
  # Monkey patch kernel to ensure that all `require` calls call the same
@@ -27,7 +27,7 @@ module Kernel
27
27
  if Pathname.new(file).absolute?
28
28
  dead_end_original_require file
29
29
  else
30
- dead_end_original_require File.expand_path("../#{file}", caller_locations(1, 1)[0].absolute_path)
30
+ dead_end_original_require File.expand_path("../#{file}", Kernel.caller_locations(1, 1)[0].absolute_path)
31
31
  end
32
32
  rescue SyntaxError => e
33
33
  DeadEnd.handle_error(e)
@@ -40,6 +40,7 @@ end
40
40
  # am I doing something different?
41
41
  class Object
42
42
  private
43
+
43
44
  def load(path, wrap = false)
44
45
  Kernel.load(path, wrap)
45
46
  rescue SyntaxError => e
@@ -52,53 +53,3 @@ class Object
52
53
  DeadEnd.handle_error(e)
53
54
  end
54
55
  end
55
-
56
- module DeadEnd
57
- IsProduction = -> {
58
- ENV["RAILS_ENV"] == "production" || ENV["RACK_ENV"] == "production"
59
- }
60
- end
61
-
62
- # Unlike a syntax error, a NoMethodError can occur hundreds or thousands of times and
63
- # chew up CPU and other resources. Since this is primarilly a "development" optimization
64
- # we can attempt to disable this behavior in a production context.
65
- if !DeadEnd::IsProduction.call
66
- class NoMethodError
67
- alias :dead_end_original_to_s :to_s
68
-
69
- def to_s
70
- return super if DeadEnd::IsProduction.call
71
-
72
- file, line, _ = backtrace[0].split(":")
73
- return super if !File.exist?(file)
74
-
75
- index = line.to_i - 1
76
- source = File.read(file)
77
- code_lines = DeadEnd::CodeLine.parse(source)
78
-
79
- block = DeadEnd::CodeBlock.new(lines: code_lines[index])
80
- lines = DeadEnd::CaptureCodeContext.new(
81
- blocks: block,
82
- code_lines: code_lines
83
- ).call
84
-
85
- message = super.dup
86
- message << $/
87
- message << $/
88
-
89
- message << DeadEnd::DisplayCodeWithLineNumbers.new(
90
- lines: lines,
91
- highlight_lines: block.lines,
92
- terminal: self.class.to_tty?
93
- ).call
94
-
95
- message << $/
96
- message
97
- rescue => e
98
- puts "DeadEnd Internal error: #{e.dead_end_original_to_s}"
99
- puts "DeadEnd Internal backtrace:"
100
- puts backtrace.map {|l| " " + l }.join($/)
101
- super
102
- end
103
- end
104
- end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module DeadEnd
3
4
  # This class is responsible for taking a code block that exists
4
5
  # at a far indentaion and then iteratively increasing the block
@@ -30,7 +31,7 @@ module DeadEnd
30
31
  # end
31
32
  #
32
33
  class BlockExpand
33
- def initialize(code_lines: )
34
+ def initialize(code_lines:)
34
35
  @code_lines = code_lines
35
36
  end
36
37
 
@@ -43,7 +44,7 @@ module DeadEnd
43
44
  end
44
45
 
45
46
  def expand_indent(block)
46
- block = AroundBlockScan.new(code_lines: @code_lines, block: block)
47
+ AroundBlockScan.new(code_lines: @code_lines, block: block)
47
48
  .skip(:hidden?)
48
49
  .stop_after_kw
49
50
  .scan_adjacent_indent
@@ -59,15 +60,15 @@ module DeadEnd
59
60
  # Slurp up empties
60
61
  if grab_empty
61
62
  scan = AroundBlockScan.new(code_lines: @code_lines, block: scan.code_block)
62
- .scan_while {|line| line.empty? || line.hidden? }
63
+ .scan_while { |line| line.empty? || line.hidden? }
63
64
  end
64
65
 
65
66
  new_block = scan.code_block
66
67
 
67
68
  if block.lines == new_block.lines
68
- return nil
69
+ nil
69
70
  else
70
- return new_block
71
+ new_block
71
72
  end
72
73
  end
73
74
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeadEnd
4
-
5
4
  # Given a block, this method will capture surrounding
6
5
  # code to give the user more context for the location of
7
6
  # the problem.
@@ -26,7 +25,7 @@ module DeadEnd
26
25
  class CaptureCodeContext
27
26
  attr_reader :code_lines
28
27
 
29
- def initialize(blocks: , code_lines:)
28
+ def initialize(blocks:, code_lines:)
30
29
  @blocks = Array(blocks)
31
30
  @code_lines = code_lines
32
31
  @visible_lines = @blocks.map(&:visible_lines).flatten
@@ -45,13 +44,13 @@ module DeadEnd
45
44
  @lines_to_output.uniq!
46
45
  @lines_to_output.sort!
47
46
 
48
- return @lines_to_output
47
+ @lines_to_output
49
48
  end
50
49
 
51
50
  def capture_falling_indent(block)
52
51
  AroundBlockScan.new(
53
52
  block: block,
54
- code_lines: @code_lines,
53
+ code_lines: @code_lines
55
54
  ).on_falling_indent do |line|
56
55
  @lines_to_output << line
57
56
  end
@@ -67,50 +66,61 @@ module DeadEnd
67
66
  @lines_to_output.concat(around_lines)
68
67
  end
69
68
 
70
- # Problems heredocs are back in play
69
+ # When there is an invalid with a keyword
70
+ # right before an end, it's unclear where
71
+ # the correct code should be.
72
+ #
73
+ # Take this example:
74
+ #
75
+ # class Dog # 1
76
+ # def bark # 2
77
+ # puts "woof" # 3
78
+ # end # 4
79
+ #
80
+ # However due to https://github.com/zombocom/dead_end/issues/32
81
+ # the problem line will be identified as:
82
+ #
83
+ # ❯ class Dog # 1
84
+ #
85
+ # Because lines 2, 3, and 4 are technically valid code and are expanded
86
+ # first, deemed valid, and hidden. We need to un-hide the matching end
87
+ # line 4. Also work backwards and if there's a mis-matched keyword, show it
88
+ # too
71
89
  def capture_last_end_same_indent(block)
72
90
  start_index = block.visible_lines.first.index
73
91
  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
92
 
77
- # TODO handle case of heredocs showing up here
93
+ # Find first end with same indent
94
+ # (this would return line 4)
78
95
  #
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?
96
+ # end # 4
97
+ matching_end = lines.find { |line| line.indent == block.current_indent && line.is_end? }
98
+ return unless matching_end
99
99
 
100
- stop_next = true if !kw_count.zero? && kw_count >= end_count
101
- true
102
- end.reverse
100
+ @lines_to_output << matching_end
103
101
 
104
- next unless kw_count > end_count
102
+ lines = @code_lines[start_index..matching_end.index]
105
103
 
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
104
+ # Work backwards from the end to
105
+ # see if there are mis-matched
106
+ # keyword/end pairs
107
+ #
108
+ # Return the first mis-matched keyword
109
+ # this would find line 2
110
+ #
111
+ # def bark # 2
112
+ # puts "woof" # 3
113
+ # end # 4
114
+ end_count = 0
115
+ kw_count = 0
116
+ kw_line = lines.reverse.detect do |line|
117
+ end_count += 1 if line.is_end?
118
+ kw_count += 1 if line.is_kw?
119
+
120
+ !kw_count.zero? && kw_count >= end_count
113
121
  end
122
+ return unless kw_line
123
+ @lines_to_output << kw_line
114
124
  end
115
125
  end
116
126
  end
@@ -54,11 +54,11 @@ module DeadEnd
54
54
  # populate an array with multiple code blocks then call `sort!`
55
55
  # on it without having to specify the sorting criteria
56
56
  def <=>(other)
57
- out = self.current_indent <=> other.current_indent
57
+ out = current_indent <=> other.current_indent
58
58
  return out if out != 0
59
59
 
60
60
  # Stable sort
61
- self.starts_at <=> other.starts_at
61
+ starts_at <=> other.starts_at
62
62
  end
63
63
 
64
64
  def current_indent
@@ -71,7 +71,7 @@ module DeadEnd
71
71
 
72
72
  def valid?
73
73
  return @valid if @valid != UNSET
74
- @valid = DeadEnd.valid?(self.to_s)
74
+ @valid = DeadEnd.valid?(to_s)
75
75
  end
76
76
 
77
77
  def to_s
@@ -39,7 +39,7 @@ module DeadEnd
39
39
  #
40
40
  # CodeFrontier#detect_invalid_blocks
41
41
  class CodeFrontier
42
- def initialize(code_lines: )
42
+ def initialize(code_lines:)
43
43
  @code_lines = code_lines
44
44
  @frontier = []
45
45
  @unvisited_lines = @code_lines.sort_by(&:indent_index)
@@ -66,7 +66,7 @@ module DeadEnd
66
66
 
67
67
  # Returns a code block with the largest indentation possible
68
68
  def pop
69
- return @frontier.pop
69
+ @frontier.pop
70
70
  end
71
71
 
72
72
  def next_indent_line
@@ -78,7 +78,7 @@ module DeadEnd
78
78
  return true if @unvisited_lines.empty?
79
79
 
80
80
  frontier_indent = @frontier.last.current_indent
81
- unvisited_indent= next_indent_line.indent
81
+ unvisited_indent = next_indent_line.indent
82
82
 
83
83
  if ENV["DEBUG"]
84
84
  puts "```"
@@ -106,7 +106,7 @@ module DeadEnd
106
106
  register_indent_block(block)
107
107
 
108
108
  # Make sure we don't double expand, if a code block fully engulfs another code block, keep the bigger one
109
- @frontier.reject! {|b|
109
+ @frontier.reject! { |b|
110
110
  b.starts_at >= block.starts_at && b.ends_at <= block.ends_at
111
111
  }
112
112
  @frontier << block
@@ -39,7 +39,7 @@ module DeadEnd
39
39
 
40
40
  attr_reader :line, :index, :indent, :original_line
41
41
 
42
- def initialize(line: , index:)
42
+ def initialize(line:, index:)
43
43
  @original_line = line.freeze
44
44
  @line = @original_line
45
45
  if line.strip.empty?
@@ -64,25 +64,25 @@ module DeadEnd
64
64
  next unless lex.type == :on_kw
65
65
 
66
66
  case lex.token
67
- when 'if', 'unless', 'while', 'until'
67
+ when "if", "unless", "while", "until"
68
68
  # Only count if/unless when it's not a "trailing" if/unless
69
69
  # https://github.com/ruby/ruby/blob/06b44f819eb7b5ede1ff69cecb25682b56a1d60c/lib/irb/ruby-lex.rb#L374-L375
70
- kw_count += 1 if !lex.expr_label?
71
- when 'def', 'case', 'for', 'begin', 'class', 'module', 'do'
70
+ kw_count += 1 unless lex.expr_label?
71
+ when "def", "case", "for", "begin", "class", "module", "do"
72
72
  kw_count += 1
73
- when 'end'
73
+ when "end"
74
74
  end_count += 1
75
75
  end
76
76
  end
77
77
 
78
- @is_comment = lex_array.detect {|lex| lex.type != :on_sp}&.type == :on_comment
78
+ @is_comment = lex_array.detect { |lex| lex.type != :on_sp }&.type == :on_comment
79
79
  return if @is_comment
80
80
  @is_kw = (kw_count - end_count) > 0
81
81
  @is_end = (end_count - kw_count) > 0
82
82
  @is_trailing_slash = lex_array.last.token == TRAILING_SLASH
83
83
  end
84
84
 
85
- alias :original :original_line
85
+ alias_method :original, :original_line
86
86
 
87
87
  def trailing_slash?
88
88
  @is_trailing_slash
@@ -92,8 +92,8 @@ module DeadEnd
92
92
  @indent_index ||= [indent, index]
93
93
  end
94
94
 
95
- def <=>(b)
96
- self.index <=> b.index
95
+ def <=>(other)
96
+ index <=> other.index
97
97
  end
98
98
 
99
99
  def is_comment?
@@ -133,7 +133,7 @@ module DeadEnd
133
133
  def line_number
134
134
  index + 1
135
135
  end
136
- alias :number :line_number
136
+ alias_method :number, :line_number
137
137
 
138
138
  def not_empty?
139
139
  !empty?
@@ -144,7 +144,7 @@ module DeadEnd
144
144
  end
145
145
 
146
146
  def to_s
147
- self.line
147
+ line
148
148
  end
149
149
  end
150
150
  end
@@ -25,14 +25,21 @@ module DeadEnd
25
25
  # # => ["def lol\n"]
26
26
  #
27
27
  class CodeSearch
28
- private; attr_reader :frontier; public
29
- public; attr_reader :invalid_blocks, :record_dir, :code_lines
28
+ private
29
+
30
+ attr_reader :frontier
31
+
32
+ public
33
+
34
+ public
35
+
36
+ attr_reader :invalid_blocks, :record_dir, :code_lines
30
37
 
31
38
  def initialize(source, record_dir: ENV["DEAD_END_RECORD_DIR"] || ENV["DEBUG"] ? "tmp" : nil)
32
39
  @source = source
33
40
  if record_dir
34
- @time = Time.now.strftime('%Y-%m-%d-%H-%M-%s-%N')
35
- @record_dir = Pathname(record_dir).join(@time).tap {|p| p.mkpath }
41
+ @time = Time.now.strftime("%Y-%m-%d-%H-%M-%s-%N")
42
+ @record_dir = Pathname(record_dir).join(@time).tap { |p| p.mkpath }
36
43
  @write_count = 0
37
44
  end
38
45
  code_lines = source.lines.map.with_index do |line, i|
@@ -43,7 +50,7 @@ module DeadEnd
43
50
 
44
51
  @frontier = CodeFrontier.new(code_lines: @code_lines)
45
52
  @invalid_blocks = []
46
- @name_tick = Hash.new {|hash, k| hash[k] = 0 }
53
+ @name_tick = Hash.new { |hash, k| hash[k] = 0 }
47
54
  @tick = 0
48
55
  @block_expand = BlockExpand.new(code_lines: code_lines)
49
56
  @parse_blocks_from_indent_line = ParseBlocksFromIndentLine.new(code_lines: @code_lines)
@@ -51,13 +58,13 @@ module DeadEnd
51
58
 
52
59
  # Used for debugging
53
60
  def record(block:, name: "record")
54
- return if !@record_dir
61
+ return unless @record_dir
55
62
  @name_tick[name] += 1
56
63
  filename = "#{@write_count += 1}-#{name}-#{@name_tick[name]}.txt"
57
64
  if ENV["DEBUG"]
58
65
  puts "\n\n==== #{filename} ===="
59
66
  puts "\n```#{block.starts_at}:#{block.ends_at}"
60
- puts "#{block.to_s}"
67
+ puts block.to_s
61
68
  puts "```"
62
69
  puts " block indent: #{block.current_indent}"
63
70
  end
@@ -65,25 +72,21 @@ module DeadEnd
65
72
  display = DisplayInvalidBlocks.new(
66
73
  blocks: block,
67
74
  terminal: false,
68
- code_lines: @code_lines,
75
+ code_lines: @code_lines
69
76
  )
70
- f.write(display.indent display.code_with_lines)
77
+ f.write(display.indent(display.code_with_lines))
71
78
  end
72
79
  end
73
80
 
74
- def push(block, name: )
81
+ def push(block, name:)
75
82
  record(block: block, name: name)
76
83
 
77
- if block.valid?
78
- block.mark_invisible
79
- frontier << block
80
- else
81
- frontier << block
82
- end
84
+ block.mark_invisible if block.valid?
85
+ frontier << block
83
86
  end
84
87
 
85
88
  # Removes the block without putting it back in the frontier
86
- def sweep(block:, name: )
89
+ def sweep(block:, name:)
87
90
  record(block: block, name: name)
88
91
 
89
92
  block.lines.each(&:mark_invisible)
@@ -149,8 +152,8 @@ module DeadEnd
149
152
  end
150
153
  end
151
154
 
152
- @invalid_blocks.concat(frontier.detect_invalid_blocks )
153
- @invalid_blocks.sort_by! {|block| block.starts_at }
155
+ @invalid_blocks.concat(frontier.detect_invalid_blocks)
156
+ @invalid_blocks.sort_by! { |block| block.starts_at }
154
157
  self
155
158
  end
156
159
  end
@@ -23,10 +23,10 @@ module DeadEnd
23
23
  TERMINAL_HIGHLIGHT = "\e[1;3m" # Bold, italics
24
24
  TERMINAL_END = "\e[0m"
25
25
 
26
- def initialize(lines: , highlight_lines: [], terminal: false)
26
+ def initialize(lines:, highlight_lines: [], terminal: false)
27
27
  @lines = Array(lines).sort
28
28
  @terminal = terminal
29
- @highlight_line_hash = Array(highlight_lines).each_with_object({}) {|line, h| h[line] = true }
29
+ @highlight_line_hash = Array(highlight_lines).each_with_object({}) { |line, h| h[line] = true }
30
30
  @digit_count = @lines.last&.line_number.to_s.length
31
31
  end
32
32
 
@@ -48,12 +48,12 @@ module DeadEnd
48
48
  end.join
49
49
  end
50
50
 
51
- private def format(contents: , number: , highlight: false, empty:)
52
- string = String.new("")
53
- if highlight
54
- string << "❯ "
51
+ private def format(contents:, number:, empty:, highlight: false)
52
+ string = +""
53
+ string << if highlight
54
+ "❯ "
55
55
  else
56
- string << " "
56
+ " "
57
57
  end
58
58
 
59
59
  string << number.rjust(@digit_count).to_s
@@ -8,7 +8,7 @@ module DeadEnd
8
8
  class DisplayInvalidBlocks
9
9
  attr_reader :filename
10
10
 
11
- def initialize(code_lines: ,blocks:, io: $stderr, filename: nil, terminal: false, invalid_obj: WhoDisSyntaxError::Null.new)
11
+ def initialize(code_lines:, blocks:, io: $stderr, filename: nil, terminal: false, invalid_obj: WhoDisSyntaxError::Null.new)
12
12
  @terminal = terminal
13
13
  @filename = filename
14
14
  @io = io
@@ -78,22 +78,21 @@ module DeadEnd
78
78
  <<~EOM
79
79
  DeadEnd: Unmatched `}` character detected
80
80
 
81
- This code has an unmatched `}`. Ensure that opening curl braces are
81
+ This code has an unmatched `}`. Ensure that opening curly braces are
82
82
  closed: `{ }`.
83
83
  EOM
84
84
  else
85
- "DeadEnd: Unmatched #{@invalid_obj.unmatched_symbol}` detected"
85
+ "DeadEnd: Unmatched `#{@invalid_obj.unmatched_symbol}` detected"
86
86
  end
87
87
  end
88
-
89
88
  end
90
89
 
91
90
  def indent(string, with: " ")
92
- string.each_line.map {|l| with + l }.join
91
+ string.each_line.map { |l| with + l }.join
93
92
  end
94
93
 
95
94
  def code_block
96
- string = String.new("")
95
+ string = +""
97
96
  string << code_with_context
98
97
  string
99
98
  end
@@ -107,7 +106,7 @@ module DeadEnd
107
106
  DisplayCodeWithLineNumbers.new(
108
107
  lines: lines,
109
108
  terminal: @terminal,
110
- highlight_lines: @invalid_lines,
109
+ highlight_lines: @invalid_lines
111
110
  ).call
112
111
  end
113
112
 
@@ -115,7 +114,7 @@ module DeadEnd
115
114
  DisplayCodeWithLineNumbers.new(
116
115
  lines: @code_lines.select(&:visible?),
117
116
  terminal: @terminal,
118
- highlight_lines: @invalid_lines,
117
+ highlight_lines: @invalid_lines
119
118
  ).call
120
119
  end
121
120
  end
data/lib/dead_end/fyi.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  require_relative "../dead_end/internals"
2
2
 
3
- require_relative "auto.rb"
3
+ require_relative "auto"
4
4
 
5
5
  DeadEnd.send(:remove_const, :SEARCH_SOURCE_ON_ERROR_DEFAULT)
6
6
  DeadEnd::SEARCH_SOURCE_ON_ERROR_DEFAULT = false
7
-
@@ -3,9 +3,13 @@
3
3
  module DeadEnd
4
4
  # Takes in a source, and returns blocks containing each heredoc
5
5
  class HeredocBlockParse
6
- private; attr_reader :code_lines, :lex; public
6
+ private
7
7
 
8
- def initialize(source:, code_lines: )
8
+ attr_reader :code_lines, :lex
9
+
10
+ public
11
+
12
+ def initialize(source:, code_lines:)
9
13
  @code_lines = code_lines
10
14
  @lex = LexAll.new(source: source)
11
15
  end
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  #
3
4
  # This is the top level file, but is moved to `internals`
4
5
  # so the top level file can instead enable the "automatic" behavior
5
6
 
6
7
  require_relative "version"
7
8
 
8
- require 'tmpdir'
9
- require 'stringio'
10
- require 'pathname'
11
- require 'ripper'
12
- require 'timeout'
9
+ require "tmpdir"
10
+ require "stringio"
11
+ require "pathname"
12
+ require "ripper"
13
+ require "timeout"
13
14
 
14
15
  module DeadEnd
15
16
  class Error < StandardError; end
@@ -17,27 +18,27 @@ module DeadEnd
17
18
  TIMEOUT_DEFAULT = ENV.fetch("DEAD_END_TIMEOUT", 5).to_i
18
19
 
19
20
  def self.handle_error(e, search_source_on_error: SEARCH_SOURCE_ON_ERROR_DEFAULT)
20
- raise e if !e.message.include?("end-of-input")
21
+ raise e unless e.message.include?("end-of-input")
21
22
 
22
23
  filename = e.message.split(":").first
23
24
 
24
25
  $stderr.sync = true
25
- $stderr.puts "Run `$ dead_end #{filename}` for more options\n"
26
+ warn "Run `$ dead_end #{filename}` for more options\n"
26
27
 
27
28
  if search_source_on_error
28
- self.call(
29
+ call(
29
30
  source: Pathname(filename).read,
30
31
  filename: filename,
31
- terminal: true,
32
+ terminal: true
32
33
  )
33
34
  end
34
35
 
35
- $stderr.puts ""
36
- $stderr.puts ""
36
+ warn ""
37
+ warn ""
37
38
  raise e
38
39
  end
39
40
 
40
- def self.call(source: , filename: , terminal: false, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
41
+ def self.call(source:, filename:, terminal: false, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
41
42
  search = nil
42
43
  Timeout.timeout(timeout) do
43
44
  record_dir ||= ENV["DEBUG"] ? "tmp" : nil
@@ -82,13 +83,13 @@ module DeadEnd
82
83
  # ) # => true
83
84
  #
84
85
  # DeadEnd.valid?(code_lines) # => false
85
- def self.valid_without?(without_lines: , code_lines:)
86
+ def self.valid_without?(without_lines:, code_lines:)
86
87
  lines = code_lines - Array(without_lines).flatten
87
88
 
88
89
  if lines.empty?
89
- return true
90
+ true
90
91
  else
91
- return valid?(lines)
92
+ valid?(lines)
92
93
  end
93
94
  end
94
95
 
@@ -137,7 +138,6 @@ module DeadEnd
137
138
  !invalid?(source)
138
139
  end
139
140
 
140
-
141
141
  def self.invalid_type(source)
142
142
  WhoDisSyntaxError.new(source).call
143
143
  end
@@ -8,20 +8,20 @@ module DeadEnd
8
8
  class LexAll
9
9
  include Enumerable
10
10
 
11
- def initialize(source: )
11
+ def initialize(source:)
12
12
  @lex = Ripper.lex(source)
13
- lineno = @lex.last&.first&.first + 1
13
+ lineno = @lex.last.first.first + 1
14
14
  source_lines = source.lines
15
15
  last_lineno = source_lines.count
16
16
 
17
17
  until lineno >= last_lineno
18
- lines = source_lines[lineno..-1]
18
+ lines = source_lines[lineno..]
19
19
 
20
- @lex.concat(Ripper.lex(lines.join, '-', lineno + 1))
21
- lineno = @lex.last&.first&.first + 1
20
+ @lex.concat(Ripper.lex(lines.join, "-", lineno + 1))
21
+ lineno = @lex.last.first.first + 1
22
22
  end
23
23
 
24
- @lex.map! {|(line, _), type, token, state| LexValue.new(line, _, type, token, state) }
24
+ @lex.map! { |(line, _), type, token, state| LexValue.new(line, type, token, state) }
25
25
  end
26
26
 
27
27
  def each
@@ -49,7 +49,7 @@ module DeadEnd
49
49
  class LexValue
50
50
  attr_reader :line, :type, :token, :state
51
51
 
52
- def initialize(line, _, type, token, state)
52
+ def initialize(line, type, token, state)
53
53
  @line = line
54
54
  @type = type
55
55
  @token = token
@@ -29,7 +29,7 @@ module DeadEnd
29
29
  class ParseBlocksFromIndentLine
30
30
  attr_reader :code_lines
31
31
 
32
- def initialize(code_lines: )
32
+ def initialize(code_lines:)
33
33
  @code_lines = code_lines
34
34
  end
35
35
 
@@ -38,7 +38,7 @@ module DeadEnd
38
38
  scan = AroundBlockScan.new(code_lines: code_lines, block: CodeBlock.new(lines: target_line))
39
39
  .skip(:empty?)
40
40
  .skip(:hidden?)
41
- .scan_while {|line| line.indent >= target_line.indent }
41
+ .scan_while { |line| line.indent >= target_line.indent }
42
42
 
43
43
  neighbors = scan.code_block.lines
44
44
 
@@ -53,4 +53,3 @@ module DeadEnd
53
53
  end
54
54
  end
55
55
  end
56
-
@@ -27,7 +27,7 @@ module DeadEnd
27
27
  @trailing_lines = []
28
28
  @code_lines.select(&:trailing_slash?).each do |trailing|
29
29
  stop_next = false
30
- lines = @code_lines[trailing.index..-1].take_while do |line|
30
+ lines = @code_lines[trailing.index..].take_while do |line|
31
31
  next false if stop_next
32
32
 
33
33
  if !line.trailing_slash?
@@ -47,7 +47,7 @@ module DeadEnd
47
47
  lines.each(&:mark_invisible)
48
48
  end
49
49
 
50
- return @code_lines_dup
50
+ @code_lines_dup
51
51
  end
52
52
  end
53
53
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeadEnd
4
- VERSION = "1.1.4"
4
+ VERSION = "1.2.0"
5
5
  end
@@ -9,8 +9,13 @@ module DeadEnd
9
9
  # # => :missing_end
10
10
  class WhoDisSyntaxError < Ripper
11
11
  class Null
12
- def error_symbol; :missing_end; end
13
- def unmatched_symbol; :end ; end
12
+ def error_symbol
13
+ :missing_end
14
+ end
15
+
16
+ def unmatched_symbol
17
+ :end
18
+ end
14
19
  end
15
20
  attr_reader :error, :run_once
16
21
 
@@ -42,22 +47,25 @@ module DeadEnd
42
47
  end
43
48
 
44
49
  def on_parse_error(msg)
50
+ return if @error_symbol && @unmatched_symbol
51
+
45
52
  @error = msg
46
53
  @unmatched_symbol = :unknown
47
54
 
48
- if @error.match?(/unexpected end-of-input/)
55
+ case @error
56
+ when /unexpected end-of-input/
49
57
  @error_symbol = :missing_end
50
- elsif @error.match?(/expecting end-of-input/)
51
- @error_symbol = :unmatched_syntax
58
+ when /expecting end-of-input/
52
59
  @unmatched_symbol = :end
53
- elsif @error.match?(/unexpected `end'/) || # Ruby 2.7 & 3.0
54
- @error.match?(/unexpected end,/) || # Ruby 2.6
55
- @error.match?(/unexpected keyword_end/) # Ruby 2.5
56
-
57
60
  @error_symbol = :unmatched_syntax
61
+ when /unexpected .* expecting '(?<unmatched_symbol>.*)'/
62
+ @unmatched_symbol = $1.to_sym if $1
63
+ @error_symbol = :unmatched_syntax
64
+ when /unexpected `end'/, # Ruby 2.7 and 3.0
65
+ /unexpected end/, # Ruby 2.6
66
+ /unexpected keyword_end/i # Ruby 2.5
58
67
 
59
- match = @error.match(/expecting '(?<unmatched_symbol>.*)'/)
60
- @unmatched_symbol = match[:unmatched_symbol].to_sym if match
68
+ @error_symbol = :unmatched_syntax
61
69
  else
62
70
  @error_symbol = :unknown
63
71
  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.1.4
4
+ version: 1.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: 2021-01-26 00:00:00.000000000 Z
11
+ date: 2021-10-08 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
@@ -23,7 +23,7 @@ files:
23
23
  - ".github/workflows/check_changelog.yml"
24
24
  - ".gitignore"
25
25
  - ".rspec"
26
- - ".travis.yml"
26
+ - ".standard.yml"
27
27
  - CHANGELOG.md
28
28
  - CODE_OF_CONDUCT.md
29
29
  - Gemfile
@@ -75,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
75
  - !ruby/object:Gem::Version
76
76
  version: '0'
77
77
  requirements: []
78
- rubygems_version: 3.0.3
78
+ rubygems_version: 3.2.22
79
79
  signing_key:
80
80
  specification_version: 4
81
81
  summary: Find syntax errors in your source in a snap
data/.travis.yml DELETED
@@ -1,6 +0,0 @@
1
- ---
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.7.2
6
- before_install: gem install bundler -v 2.1.4