dead_end 1.0.2 → 1.1.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: 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