dead_end 1.0.1 → 1.1.3

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: 00641720122b144830e8eccf13485745712d861158a7b928969cfcbadb52d53e
4
+ data.tar.gz: b9f874797083cca9ac2925023890553f1af3cbafbd52e6719a073de9b236bad7
5
5
  SHA512:
6
- metadata.gz: 89e7c7734a7a74c735479d35418ffda7d53557ce7d0c8a47fb2a67f5581d87d1be8117ff62d6590ed2cbf633fb566b797a6f3c3033e6c6a50b543cfbba9f4718
7
- data.tar.gz: 2804e68394e928f36803859eb0e193bf96dc088138c4fea87510d6bc0d70e9c5485b6ca4c402762174c1ba3b139b9a4d1af28a6b31e87ec4e36859f659afc42d
6
+ metadata.gz: c9cfe06766beb27cc43a7197a665e9245c2b1de0343679699c98f047afb207ad64a52855102e9fda5071c7ad7ca767fc2b29f986eb6f030250e9613c0615d879
7
+ data.tar.gz: c19544c98f18954a5166c88bcc2987ee9e30db2344753f8aa1cc85645e4328bcc2442fd0b495c5c4bbd19af094760cfa180a1c4f017f395941cd844c3274ad70
@@ -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,27 @@
1
1
  ## HEAD (unreleased)
2
2
 
3
+ ## 1.1.3
4
+
5
+ - Add compatibility with zeitwerk (https://github.com/zombocom/dead_end/pull/52)
6
+
7
+ ## 1.1.2
8
+
9
+ - Namespace Kernel method aliases (https://github.com/zombocom/dead_end/pull/51)
10
+
11
+ ## 1.1.1
12
+
13
+ - Safer NoMethodError annotation (https://github.com/zombocom/dead_end/pull/48)
14
+
15
+ ## 1.1.0
16
+
17
+ - Annotate NoMethodError in non-production environments (https://github.com/zombocom/dead_end/pull/46)
18
+ - Do not count trailing if/unless as a keyword (https://github.com/zombocom/dead_end/pull/44)
19
+
20
+ ## 1.0.2
21
+
22
+ - Fix bug where empty lines were interpreted to have a zero indentation (https://github.com/zombocom/dead_end/pull/39)
23
+ - 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)
24
+
3
25
  ## 1.0.1
4
26
 
5
27
  - 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.1.3)
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.6
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
 
@@ -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 }
@@ -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
@@ -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
 
@@ -31,14 +31,25 @@ 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:)
37
43
  @original_line = line.freeze
38
44
  @line = @original_line
39
- @empty = line.strip.empty?
45
+ if line.strip.empty?
46
+ @empty = true
47
+ @indent = 0
48
+ else
49
+ @empty = false
50
+ @indent = SpaceCount.indent(line)
51
+ end
40
52
  @index = index
41
- @indent = SpaceCount.indent(line)
42
53
  @status = nil # valid, invalid, unknown
43
54
  @invalid = false
44
55
 
@@ -46,24 +57,29 @@ module DeadEnd
46
57
  end
47
58
 
48
59
  private def lex_detect!
49
- lex = LexAll.new(source: line)
60
+ lex_array = LexAll.new(source: line)
50
61
  kw_count = 0
51
62
  end_count = 0
52
- lex.each do |lex|
63
+ lex_array.each_with_index do |lex, index|
53
64
  next unless lex.type == :on_kw
54
65
 
55
66
  case lex.token
56
- 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'
57
72
  kw_count += 1
58
73
  when 'end'
59
74
  end_count += 1
60
75
  end
61
76
  end
62
77
 
78
+ @is_comment = lex_array.detect {|lex| lex.type != :on_sp}&.type == :on_comment
79
+ return if @is_comment
63
80
  @is_kw = (kw_count - end_count) > 0
64
81
  @is_end = (end_count - kw_count) > 0
65
- @is_comment = lex.detect {|lex| lex.type != :on_sp}&.type == :on_comment
66
- @is_trailing_slash = lex.last.token == TRAILING_SLASH
82
+ @is_trailing_slash = lex_array.last.token == TRAILING_SLASH
67
83
  end
68
84
 
69
85
  alias :original :original_line
@@ -72,6 +88,10 @@ module DeadEnd
72
88
  @is_trailing_slash
73
89
  end
74
90
 
91
+ def indent_index
92
+ @indent_index ||= [indent, index]
93
+ end
94
+
75
95
  def <=>(b)
76
96
  self.index <=> b.index
77
97
  end
@@ -92,15 +112,6 @@ module DeadEnd
92
112
  @is_end
93
113
  end
94
114
 
95
- def mark_invalid
96
- @invalid = true
97
- self
98
- end
99
-
100
- def marked_invalid?
101
- @invalid
102
- end
103
-
104
115
  def mark_invisible
105
116
  @line = ""
106
117
  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
 
@@ -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.1"
4
+ VERSION = "1.1.3"
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.1.3
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: 2021-01-21 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.2.3
80
80
  signing_key:
81
81
  specification_version: 4
82
82
  summary: Find syntax errors in your source in a snap