dead_end 1.0.2 → 1.1.0

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: 125719d74f88dcc5d196a1570508bd36d83d7ece6058b029a579da90a70cc099
4
- data.tar.gz: 2eea6be22997101b46ff2820dfabaef4e473424a552e7f9c2835e43471c4431b
3
+ metadata.gz: 8b372fd8bb6cb9f7b3a601fcb32a539ae361fa19e3f64471d6c446564532750c
4
+ data.tar.gz: fc78d6a4ff9f86663af64dc6acf1bf2a0bced8fded065a2e0b52210e19766496
5
5
  SHA512:
6
- metadata.gz: 674e35c603ec9ed2a8662e6ee3472381c6108a8734f455d39ef30472e38d13fd08a23cb107333f9ff8353ace614cb6c1f5bcb21e6aeb5384911ad1a61eb1ff58
7
- data.tar.gz: 4ed5351764e96b5c95b2c4f68403bd81a3739163ad83d9e473829a1f6571f7e3614b855f56f21f5e8f15434817f7b7e8a947146044d4ac4c4f9314a15cd6b9bf
6
+ metadata.gz: 5ee131a4f8574bec7a8e9054b4085524cf49692afeb40387144f9461f34323b533944b1c9683b0772c43858ed348fb6eb889ae89ecfb765b0c26b805e93dee4c
7
+ data.tar.gz: bffe41e67a11e342ad5db8a36b7faa81c1ea24eab2a37bb67c77762cd0be470f12cc55b8baf9e9696e2ed321943eed348328b637220102d2e9e1678c17ef3d18
@@ -1,5 +1,10 @@
1
1
  ## HEAD (unreleased)
2
2
 
3
+ ## 1.1.0
4
+
5
+ - Annotate NoMethodError in non-production environments (https://github.com/zombocom/dead_end/pull/46)
6
+ - Do not count trailing if/unless as a keyword (https://github.com/zombocom/dead_end/pull/44)
7
+
3
8
  ## 1.0.2
4
9
 
5
10
  - Fix bug where empty lines were interpreted to have a zero indentation (https://github.com/zombocom/dead_end/pull/39)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dead_end (1.0.2)
4
+ dead_end (1.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # DeadEnd
2
2
 
3
- An AI powered library to find syntax errors in your source code:
3
+ An error in your code forces you to stop. DeadEnd helps you find those errors to get you back on your way faster.
4
4
 
5
5
  DeadEnd: Unmatched `end` detected
6
6
 
@@ -18,7 +18,7 @@ An AI powered library to find syntax errors in your source code:
18
18
 
19
19
  ## Installation in your codebase
20
20
 
21
- To automatically search syntax errors when they happen, add this to your Gemfile:
21
+ To automatically annotate errors when they happen, add this to your Gemfile:
22
22
 
23
23
  ```ruby
24
24
  gem 'dead_end'
@@ -44,7 +44,7 @@ If you're using rspec add this to your `.rspec` file:
44
44
 
45
45
  ## Install the CLI
46
46
 
47
- To get the CLI and manually search for syntax errors, install the gem:
47
+ To get the CLI and manually search for syntax errors (but not automatically annotate them), you can manually install the gem:
48
48
 
49
49
  $ gem install dead_end
50
50
 
@@ -54,7 +54,7 @@ This gives you the CLI command `$ dead_end` for more info run `$ dead_end --help
54
54
 
55
55
  - Missing `end`:
56
56
 
57
- ```
57
+ ```ruby
58
58
  class Dog
59
59
  def bark
60
60
  puts "bark"
@@ -68,7 +68,7 @@ end
68
68
 
69
69
  - Unexpected `end`
70
70
 
71
- ```
71
+ ```ruby
72
72
  class Dog
73
73
  def speak
74
74
  @sounds.each |sound| # Note the missing `do` here
@@ -81,6 +81,21 @@ 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
+
84
99
  ## Sounds cool, but why isn't this baked into Ruby directly?
85
100
 
86
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:
@@ -90,7 +105,7 @@ I would love to get something like this directly in Ruby, but I first need to pr
90
105
 
91
106
  ## Artificial Inteligence?
92
107
 
93
- This library uses a goal-seeking algorithm similar to that of a path-finding search. For more information [read the blog post about how it works under the hood](https://schneems.com/2020/12/01/squash-unexpectedend-errors-with-syntaxsearch/).
108
+ This library uses a goal-seeking algorithm for syntax error detection similar to that of a path-finding search. For more information [read the blog post about how it works under the hood](https://schneems.com/2020/12/01/squash-unexpectedend-errors-with-syntaxsearch/).
94
109
 
95
110
  ## How does it detect syntax error locations?
96
111
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+ #
1
3
  require_relative "../dead_end/internals"
2
4
 
3
5
  # Monkey patch kernel to ensure that all `require` calls call the same
@@ -49,3 +51,49 @@ class Object
49
51
  end
50
52
  end
51
53
 
54
+ module DeadEnd
55
+ IsProduction = -> {
56
+ ENV["RAILS_ENV"] == "production" || ENV["RACK_ENV"] == "production"
57
+ }
58
+ end
59
+
60
+ # Unlike a syntax error, a NoMethodError can occur hundreds or thousands of times and
61
+ # chew up CPU and other resources. Since this is primarilly a "development" optimization
62
+ # we can attempt to disable this behavior in a production context.
63
+ if !DeadEnd::IsProduction.call
64
+ class NoMethodError
65
+ def to_s
66
+ return super if DeadEnd::IsProduction.call
67
+
68
+ file, line, _ = backtrace[0].split(":")
69
+ return super if !File.exist?(file)
70
+
71
+ index = line.to_i - 1
72
+ source = File.read(file)
73
+ code_lines = DeadEnd::CodeLine.parse(source)
74
+
75
+ block = DeadEnd::CodeBlock.new(lines: code_lines[index])
76
+ lines = DeadEnd::CaptureCodeContext.new(
77
+ blocks: block,
78
+ code_lines: code_lines
79
+ ).call
80
+
81
+ message = super.dup
82
+ message << $/
83
+ message << $/
84
+
85
+ message << DeadEnd::DisplayCodeWithLineNumbers.new(
86
+ lines: lines,
87
+ highlight_lines: block.lines,
88
+ terminal: self.class.to_tty?
89
+ ).call
90
+
91
+ message << $/
92
+ message
93
+ rescue => e
94
+ puts "DeadEnd Internal error: #{e.message}"
95
+ puts "DeadEnd Internal backtrace: #{e.backtrace}"
96
+ super
97
+ end
98
+ end
99
+ end
@@ -31,6 +31,12 @@ module DeadEnd
31
31
  class CodeLine
32
32
  TRAILING_SLASH = ("\\" + $/).freeze
33
33
 
34
+ def self.parse(source)
35
+ source.lines.map.with_index do |line, index|
36
+ CodeLine.new(line: line, index: index)
37
+ end
38
+ end
39
+
34
40
  attr_reader :line, :index, :indent, :original_line
35
41
 
36
42
  def initialize(line: , index:)
@@ -51,25 +57,29 @@ module DeadEnd
51
57
  end
52
58
 
53
59
  private def lex_detect!
54
- lex = LexAll.new(source: line)
60
+ lex_array = LexAll.new(source: line)
55
61
  kw_count = 0
56
62
  end_count = 0
57
- lex.each do |lex|
63
+ lex_array.each_with_index do |lex, index|
58
64
  next unless lex.type == :on_kw
59
65
 
60
66
  case lex.token
61
- when 'def', 'case', 'for', 'begin', 'class', 'module', 'if', 'unless', 'while', 'until' , 'do'
67
+ when 'if', 'unless', 'while', 'until'
68
+ # Only count if/unless when it's not a "trailing" if/unless
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'
62
72
  kw_count += 1
63
73
  when 'end'
64
74
  end_count += 1
65
75
  end
66
76
  end
67
77
 
68
- @is_comment = lex.detect {|lex| lex.type != :on_sp}&.type == :on_comment
78
+ @is_comment = lex_array.detect {|lex| lex.type != :on_sp}&.type == :on_comment
69
79
  return if @is_comment
70
80
  @is_kw = (kw_count - end_count) > 0
71
81
  @is_end = (end_count - kw_count) > 0
72
- @is_trailing_slash = lex.last.token == TRAILING_SLASH
82
+ @is_trailing_slash = lex_array.last.token == TRAILING_SLASH
73
83
  end
74
84
 
75
85
  alias :original :original_line
@@ -21,7 +21,7 @@ module DeadEnd
21
21
  lineno = @lex.last&.first&.first + 1
22
22
  end
23
23
 
24
- @lex.map! {|(line, _), type, token| LexValue.new(line, _, type, token) }
24
+ @lex.map! {|(line, _), type, token, state| LexValue.new(line, _, type, token, state) }
25
25
  end
26
26
 
27
27
  def each
@@ -47,11 +47,17 @@ module DeadEnd
47
47
  # lex.type # => :on_indent
48
48
  # lex.token # => "describe"
49
49
  class LexValue
50
- attr_reader :line, :type, :token
51
- def initialize(line, _, type, token)
50
+ attr_reader :line, :type, :token, :state
51
+
52
+ def initialize(line, _, type, token, state)
52
53
  @line = line
53
54
  @type = type
54
55
  @token = token
56
+ @state = state
57
+ end
58
+
59
+ def expr_label?
60
+ state.allbits?(Ripper::EXPR_LABEL)
55
61
  end
56
62
  end
57
63
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeadEnd
4
- VERSION = "1.0.2"
4
+ VERSION = "1.1.0"
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.2
4
+ version: 1.1.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-28 00:00:00.000000000 Z
11
+ date: 2020-12-29 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