dead_end 1.0.1 → 1.1.3

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: 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