dead_end 1.0.2 → 1.1.4

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: d1e8a394d94d7d8f70138a9d80d7e660cca8dc06e3d3a5eb9b74334585f284aa
4
+ data.tar.gz: 75296d7faaf4b117ad45bed40252566ee08752b622b2d90e4afa3be8b4a9b774
5
5
  SHA512:
6
- metadata.gz: 674e35c603ec9ed2a8662e6ee3472381c6108a8734f455d39ef30472e38d13fd08a23cb107333f9ff8353ace614cb6c1f5bcb21e6aeb5384911ad1a61eb1ff58
7
- data.tar.gz: 4ed5351764e96b5c95b2c4f68403bd81a3739163ad83d9e473829a1f6571f7e3614b855f56f21f5e8f15434817f7b7e8a947146044d4ac4c4f9314a15cd6b9bf
6
+ metadata.gz: f970086da7e3f662d2dabda9a59d89af73d503dc986a9d469d45b83f25184590892e5bdb0a4c174318b2d1bb201f112c81a0a281d0a530feb49dd7f0eb1fb144
7
+ data.tar.gz: cc85e81a1d996320128d7656c0cea0727f9672060d7d2747e30e77d2aeb59d3549f4dd230835c5042988c53b1252f975c472774eb2ef7f258caebea5cd5eee40
@@ -32,6 +32,14 @@ jobs:
32
32
  - ruby/install-deps
33
33
  - <<: *unit
34
34
 
35
+ "ruby-3-0":
36
+ docker:
37
+ - image: circleci/ruby:3.0
38
+ steps:
39
+ - checkout
40
+ - ruby/install-deps
41
+ - <<: *unit
42
+
35
43
  workflows:
36
44
  version: 2
37
45
  build:
@@ -39,3 +47,4 @@ workflows:
39
47
  - "ruby-2-5"
40
48
  - "ruby-2-6"
41
49
  - "ruby-2-7"
50
+ - "ruby-3-0"
@@ -1,5 +1,26 @@
1
1
  ## HEAD (unreleased)
2
2
 
3
+ ## 1.1.4
4
+
5
+ - Avoid including demo gif in built gem (https://github.com/zombocom/dead_end/pull/53)
6
+
7
+ ## 1.1.3
8
+
9
+ - Add compatibility with zeitwerk (https://github.com/zombocom/dead_end/pull/52)
10
+
11
+ ## 1.1.2
12
+
13
+ - Namespace Kernel method aliases (https://github.com/zombocom/dead_end/pull/51)
14
+
15
+ ## 1.1.1
16
+
17
+ - Safer NoMethodError annotation (https://github.com/zombocom/dead_end/pull/48)
18
+
19
+ ## 1.1.0
20
+
21
+ - Annotate NoMethodError in non-production environments (https://github.com/zombocom/dead_end/pull/46)
22
+ - Do not count trailing if/unless as a keyword (https://github.com/zombocom/dead_end/pull/44)
23
+
3
24
  ## 1.0.2
4
25
 
5
26
  - 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.4)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -33,4 +33,4 @@ DEPENDENCIES
33
33
  stackprof
34
34
 
35
35
  BUNDLED WITH
36
- 2.1.4
36
+ 2.2.3
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
 
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
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
22
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
23
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|assets)/}) }
24
24
  end
25
25
  spec.bindir = "exe"
26
26
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
@@ -1,29 +1,33 @@
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
4
6
  # method
5
7
  module Kernel
6
- alias_method :original_require, :require
7
- alias_method :original_require_relative, :require_relative
8
- alias_method :original_load, :load
8
+ module_function
9
+
10
+ alias_method :dead_end_original_require, :require
11
+ alias_method :dead_end_original_require_relative, :require_relative
12
+ alias_method :dead_end_original_load, :load
9
13
 
10
14
  def load(file, wrap = false)
11
- original_load(file)
15
+ dead_end_original_load(file)
12
16
  rescue SyntaxError => e
13
17
  DeadEnd.handle_error(e)
14
18
  end
15
19
 
16
20
  def require(file)
17
- original_require(file)
21
+ dead_end_original_require(file)
18
22
  rescue SyntaxError => e
19
23
  DeadEnd.handle_error(e)
20
24
  end
21
25
 
22
26
  def require_relative(file)
23
27
  if Pathname.new(file).absolute?
24
- original_require file
28
+ dead_end_original_require file
25
29
  else
26
- original_require File.expand_path("../#{file}", caller_locations(1, 1)[0].absolute_path)
30
+ dead_end_original_require File.expand_path("../#{file}", caller_locations(1, 1)[0].absolute_path)
27
31
  end
28
32
  rescue SyntaxError => e
29
33
  DeadEnd.handle_error(e)
@@ -49,3 +53,52 @@ class Object
49
53
  end
50
54
  end
51
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
@@ -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.4"
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.4
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: 2021-01-26 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
@@ -31,7 +31,6 @@ files:
31
31
  - LICENSE.txt
32
32
  - README.md
33
33
  - Rakefile
34
- - assets/syntax_search.gif
35
34
  - bin/console
36
35
  - bin/setup
37
36
  - dead_end.gemspec
@@ -76,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
75
  - !ruby/object:Gem::Version
77
76
  version: '0'
78
77
  requirements: []
79
- rubygems_version: 3.1.4
78
+ rubygems_version: 3.0.3
80
79
  signing_key:
81
80
  specification_version: 4
82
81
  summary: Find syntax errors in your source in a snap
Binary file