dead_end 1.1.7 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +27 -1
  3. data/.github/workflows/check_changelog.yml +14 -7
  4. data/.standard.yml +1 -0
  5. data/CHANGELOG.md +60 -0
  6. data/CODE_OF_CONDUCT.md +2 -2
  7. data/Gemfile +2 -0
  8. data/Gemfile.lock +31 -2
  9. data/README.md +122 -35
  10. data/Rakefile +1 -1
  11. data/dead_end.gemspec +12 -12
  12. data/exe/dead_end +4 -67
  13. data/lib/dead_end/{internals.rb → api.rb} +90 -52
  14. data/lib/dead_end/around_block_scan.rb +16 -18
  15. data/lib/dead_end/auto.rb +3 -101
  16. data/lib/dead_end/block_expand.rb +6 -5
  17. data/lib/dead_end/capture_code_context.rb +167 -50
  18. data/lib/dead_end/clean_document.rb +304 -0
  19. data/lib/dead_end/cli.rb +129 -0
  20. data/lib/dead_end/code_block.rb +20 -4
  21. data/lib/dead_end/code_frontier.rb +74 -29
  22. data/lib/dead_end/code_line.rb +176 -87
  23. data/lib/dead_end/code_search.rb +40 -51
  24. data/lib/dead_end/core_ext.rb +35 -0
  25. data/lib/dead_end/display_code_with_line_numbers.rb +7 -8
  26. data/lib/dead_end/display_invalid_blocks.rb +42 -80
  27. data/lib/dead_end/explain_syntax.rb +103 -0
  28. data/lib/dead_end/insertion_sort.rb +46 -0
  29. data/lib/dead_end/left_right_lex_count.rb +168 -0
  30. data/lib/dead_end/lex_all.rb +25 -34
  31. data/lib/dead_end/lex_value.rb +70 -0
  32. data/lib/dead_end/parse_blocks_from_indent_line.rb +3 -4
  33. data/lib/dead_end/pathname_from_message.rb +47 -0
  34. data/lib/dead_end/ripper_errors.rb +36 -0
  35. data/lib/dead_end/version.rb +1 -1
  36. data/lib/dead_end.rb +2 -2
  37. metadata +14 -9
  38. data/.travis.yml +0 -6
  39. data/lib/dead_end/fyi.rb +0 -7
  40. data/lib/dead_end/heredoc_block_parse.rb +0 -30
  41. data/lib/dead_end/trailing_slash_join.rb +0 -53
  42. data/lib/dead_end/who_dis_syntax_error.rb +0 -69
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeadEnd
2
4
  # Ripper.lex is not guaranteed to lex the entire source document
3
5
  #
@@ -8,20 +10,29 @@ module DeadEnd
8
10
  class LexAll
9
11
  include Enumerable
10
12
 
11
- def initialize(source: )
12
- @lex = Ripper.lex(source)
13
- lineno = @lex.last&.first&.first + 1
14
- source_lines = source.lines
15
- last_lineno = source_lines.count
13
+ def initialize(source:, source_lines: nil)
14
+ @lex = Ripper::Lexer.new(source, "-", 1).parse.sort_by(&:pos)
15
+ lineno = @lex.last.pos.first + 1
16
+ source_lines ||= source.lines
17
+ last_lineno = source_lines.length
16
18
 
17
19
  until lineno >= last_lineno
18
20
  lines = source_lines[lineno..-1]
19
21
 
20
- @lex.concat(Ripper.lex(lines.join, '-', lineno + 1))
21
- lineno = @lex.last&.first&.first + 1
22
+ @lex.concat(
23
+ Ripper::Lexer.new(lines.join, "-", lineno + 1).parse.sort_by(&:pos)
24
+ )
25
+ lineno = @lex.last.pos.first + 1
22
26
  end
23
27
 
24
- @lex.map! {|(line, _), type, token, state| LexValue.new(line, _, type, token, state) }
28
+ last_lex = nil
29
+ @lex.map! { |elem|
30
+ last_lex = LexValue.new(elem.pos.first, elem.event, elem.tok, elem.state, last_lex)
31
+ }
32
+ end
33
+
34
+ def to_a
35
+ @lex
25
36
  end
26
37
 
27
38
  def each
@@ -31,34 +42,14 @@ module DeadEnd
31
42
  end
32
43
  end
33
44
 
34
- def last
35
- @lex.last
45
+ def [](index)
46
+ @lex[index]
36
47
  end
37
48
 
38
- # Value object for accessing lex values
39
- #
40
- # This lex:
41
- #
42
- # [1, 0], :on_ident, "describe", CMDARG
43
- #
44
- # Would translate into:
45
- #
46
- # lex.line # => 1
47
- # lex.type # => :on_indent
48
- # lex.token # => "describe"
49
- class LexValue
50
- attr_reader :line, :type, :token, :state
51
-
52
- def initialize(line, _, type, token, state)
53
- @line = line
54
- @type = type
55
- @token = token
56
- @state = state
57
- end
58
-
59
- def expr_label?
60
- state.allbits?(Ripper::EXPR_LABEL)
61
- end
49
+ def last
50
+ @lex.last
62
51
  end
63
52
  end
64
53
  end
54
+
55
+ require_relative "lex_value"
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeadEnd
4
+ # Value object for accessing lex values
5
+ #
6
+ # This lex:
7
+ #
8
+ # [1, 0], :on_ident, "describe", CMDARG
9
+ #
10
+ # Would translate into:
11
+ #
12
+ # lex.line # => 1
13
+ # lex.type # => :on_indent
14
+ # lex.token # => "describe"
15
+ class LexValue
16
+ attr_reader :line, :type, :token, :state
17
+
18
+ def initialize(line, type, token, state, last_lex = nil)
19
+ @line = line
20
+ @type = type
21
+ @token = token
22
+ @state = state
23
+
24
+ set_kw_end(last_lex)
25
+ end
26
+
27
+ private def set_kw_end(last_lex)
28
+ @is_end = false
29
+ @is_kw = false
30
+ return if type != :on_kw
31
+ #
32
+ return if last_lex && last_lex.fname? # https://github.com/ruby/ruby/commit/776759e300e4659bb7468e2b97c8c2d4359a2953
33
+
34
+ case token
35
+ when "if", "unless", "while", "until"
36
+ # Only count if/unless when it's not a "trailing" if/unless
37
+ # https://github.com/ruby/ruby/blob/06b44f819eb7b5ede1ff69cecb25682b56a1d60c/lib/irb/ruby-lex.rb#L374-L375
38
+ @is_kw = true unless expr_label?
39
+ when "def", "case", "for", "begin", "class", "module", "do"
40
+ @is_kw = true
41
+ when "end"
42
+ @is_end = true
43
+ end
44
+ end
45
+
46
+ def fname?
47
+ state.allbits?(Ripper::EXPR_FNAME)
48
+ end
49
+
50
+ def ignore_newline?
51
+ type == :on_ignored_nl
52
+ end
53
+
54
+ def is_end?
55
+ @is_end
56
+ end
57
+
58
+ def is_kw?
59
+ @is_kw
60
+ end
61
+
62
+ def expr_beg?
63
+ state.anybits?(Ripper::EXPR_BEG)
64
+ end
65
+
66
+ def expr_label?
67
+ state.allbits?(Ripper::EXPR_LABEL)
68
+ end
69
+ end
70
+ end
@@ -4,7 +4,7 @@ module DeadEnd
4
4
  # This class is responsible for generating initial code blocks
5
5
  # that will then later be expanded.
6
6
  #
7
- # The biggest concern when guessing about code blocks, is accidentally
7
+ # The biggest concern when guessing code blocks, is accidentally
8
8
  # grabbing one that contains only an "end". In this example:
9
9
  #
10
10
  # def dog
@@ -29,7 +29,7 @@ module DeadEnd
29
29
  class ParseBlocksFromIndentLine
30
30
  attr_reader :code_lines
31
31
 
32
- def initialize(code_lines: )
32
+ def initialize(code_lines:)
33
33
  @code_lines = code_lines
34
34
  end
35
35
 
@@ -38,7 +38,7 @@ module DeadEnd
38
38
  scan = AroundBlockScan.new(code_lines: code_lines, block: CodeBlock.new(lines: target_line))
39
39
  .skip(:empty?)
40
40
  .skip(:hidden?)
41
- .scan_while {|line| line.indent >= target_line.indent }
41
+ .scan_while { |line| line.indent >= target_line.indent }
42
42
 
43
43
  neighbors = scan.code_block.lines
44
44
 
@@ -53,4 +53,3 @@ module DeadEnd
53
53
  end
54
54
  end
55
55
  end
56
-
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeadEnd
4
+ # Converts a SyntaxError message to a path
5
+ #
6
+ # Handles the case where the filename has a colon in it
7
+ # such as on a windows file system: https://github.com/zombocom/dead_end/issues/111
8
+ #
9
+ # Example:
10
+ #
11
+ # message = "/tmp/scratch:2:in `require_relative': /private/tmp/bad.rb:1: syntax error, unexpected `end' (SyntaxError)"
12
+ # puts PathnameFromMessage.new(message).call.name
13
+ # # => "/tmp/scratch.rb"
14
+ #
15
+ class PathnameFromMessage
16
+ attr_reader :name
17
+
18
+ def initialize(message, io: $stderr)
19
+ @line = message.lines.first
20
+ @parts = @line.split(":")
21
+ @guess = []
22
+ @name = nil
23
+ @io = io
24
+ end
25
+
26
+ def call
27
+ until stop?
28
+ @guess << @parts.shift
29
+ @name = Pathname(@guess.join(":"))
30
+ end
31
+
32
+ if @parts.empty?
33
+ @io.puts "DeadEnd: Could not find filename from #{@line.inspect}"
34
+ @name = nil
35
+ end
36
+
37
+ self
38
+ end
39
+
40
+ def stop?
41
+ return true if @parts.empty?
42
+ return false if @guess.empty?
43
+
44
+ @name&.exist?
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeadEnd
4
+ # Capture parse errors from ripper
5
+ #
6
+ # Example:
7
+ #
8
+ # puts RipperErrors.new(" def foo").call.errors
9
+ # # => ["syntax error, unexpected end-of-input, expecting ';' or '\\n'"]
10
+ class RipperErrors < Ripper
11
+ attr_reader :errors
12
+
13
+ # Comes from ripper, called
14
+ # on every parse error, msg
15
+ # is a string
16
+ def on_parse_error(msg)
17
+ @errors ||= []
18
+ @errors << msg
19
+ end
20
+
21
+ alias_method :on_alias_error, :on_parse_error
22
+ alias_method :on_assign_error, :on_parse_error
23
+ alias_method :on_class_name_error, :on_parse_error
24
+ alias_method :on_param_error, :on_parse_error
25
+ alias_method :compile_error, :on_parse_error
26
+
27
+ def call
28
+ @run_once ||= begin
29
+ @errors = []
30
+ parse
31
+ true
32
+ end
33
+ self
34
+ end
35
+ end
36
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeadEnd
4
- VERSION = "1.1.7"
4
+ VERSION = "3.1.1"
5
5
  end
data/lib/dead_end.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "dead_end/internals"
4
- require_relative "dead_end/auto"
3
+ require_relative "dead_end/api"
4
+ require_relative "dead_end/core_ext"
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.1.7
4
+ version: 3.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - schneems
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-10 00:00:00.000000000 Z
11
+ date: 2022-01-04 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
@@ -23,7 +23,7 @@ files:
23
23
  - ".github/workflows/check_changelog.yml"
24
24
  - ".gitignore"
25
25
  - ".rspec"
26
- - ".travis.yml"
26
+ - ".standard.yml"
27
27
  - CHANGELOG.md
28
28
  - CODE_OF_CONDUCT.md
29
29
  - Gemfile
@@ -36,24 +36,29 @@ files:
36
36
  - dead_end.gemspec
37
37
  - exe/dead_end
38
38
  - lib/dead_end.rb
39
+ - lib/dead_end/api.rb
39
40
  - lib/dead_end/around_block_scan.rb
40
41
  - lib/dead_end/auto.rb
41
42
  - lib/dead_end/block_expand.rb
42
43
  - lib/dead_end/capture_code_context.rb
44
+ - lib/dead_end/clean_document.rb
45
+ - lib/dead_end/cli.rb
43
46
  - lib/dead_end/code_block.rb
44
47
  - lib/dead_end/code_frontier.rb
45
48
  - lib/dead_end/code_line.rb
46
49
  - lib/dead_end/code_search.rb
50
+ - lib/dead_end/core_ext.rb
47
51
  - lib/dead_end/display_code_with_line_numbers.rb
48
52
  - lib/dead_end/display_invalid_blocks.rb
49
- - lib/dead_end/fyi.rb
50
- - lib/dead_end/heredoc_block_parse.rb
51
- - lib/dead_end/internals.rb
53
+ - lib/dead_end/explain_syntax.rb
54
+ - lib/dead_end/insertion_sort.rb
55
+ - lib/dead_end/left_right_lex_count.rb
52
56
  - lib/dead_end/lex_all.rb
57
+ - lib/dead_end/lex_value.rb
53
58
  - lib/dead_end/parse_blocks_from_indent_line.rb
54
- - lib/dead_end/trailing_slash_join.rb
59
+ - lib/dead_end/pathname_from_message.rb
60
+ - lib/dead_end/ripper_errors.rb
55
61
  - lib/dead_end/version.rb
56
- - lib/dead_end/who_dis_syntax_error.rb
57
62
  homepage: https://github.com/zombocom/dead_end.git
58
63
  licenses:
59
64
  - MIT
@@ -75,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
80
  - !ruby/object:Gem::Version
76
81
  version: '0'
77
82
  requirements: []
78
- rubygems_version: 3.2.15
83
+ rubygems_version: 3.2.32
79
84
  signing_key:
80
85
  specification_version: 4
81
86
  summary: Find syntax errors in your source in a snap
data/.travis.yml DELETED
@@ -1,6 +0,0 @@
1
- ---
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.7.2
6
- before_install: gem install bundler -v 2.1.4
data/lib/dead_end/fyi.rb DELETED
@@ -1,7 +0,0 @@
1
- require_relative "../dead_end/internals"
2
-
3
- require_relative "auto.rb"
4
-
5
- DeadEnd.send(:remove_const, :SEARCH_SOURCE_ON_ERROR_DEFAULT)
6
- DeadEnd::SEARCH_SOURCE_ON_ERROR_DEFAULT = false
7
-
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeadEnd
4
- # Takes in a source, and returns blocks containing each heredoc
5
- class HeredocBlockParse
6
- private; attr_reader :code_lines, :lex; public
7
-
8
- def initialize(source:, code_lines: )
9
- @code_lines = code_lines
10
- @lex = LexAll.new(source: source)
11
- end
12
-
13
- def call
14
- blocks = []
15
- beginning = []
16
- @lex.each do |lex|
17
- case lex.type
18
- when :on_heredoc_beg
19
- beginning << lex.line
20
- when :on_heredoc_end
21
- start_index = beginning.pop - 1
22
- end_index = lex.line - 1
23
- blocks << CodeBlock.new(lines: code_lines[start_index..end_index])
24
- end
25
- end
26
-
27
- blocks
28
- end
29
- end
30
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeadEnd
4
- # Handles code that contains trailing slashes
5
- # by turning multiple lines with trailing slash(es) into
6
- # a single code line
7
- #
8
- # expect(code_lines.join).to eq(<<~EOM)
9
- # it "trailing \
10
- # "slash" do
11
- # end
12
- # EOM
13
- #
14
- # lines = TrailngSlashJoin(code_lines: code_lines).call
15
- # expect(lines.first.to_s).to eq(<<~EOM)
16
- # it "trailing \
17
- # "slash" do
18
- # EOM
19
- #
20
- class TrailingSlashJoin
21
- def initialize(code_lines:)
22
- @code_lines = code_lines
23
- @code_lines_dup = code_lines.dup
24
- end
25
-
26
- def call
27
- @trailing_lines = []
28
- @code_lines.select(&:trailing_slash?).each do |trailing|
29
- stop_next = false
30
- lines = @code_lines[trailing.index..-1].take_while do |line|
31
- next false if stop_next
32
-
33
- if !line.trailing_slash?
34
- stop_next = true
35
- end
36
-
37
- true
38
- end
39
-
40
- joined_line = CodeLine.new(line: lines.map(&:original_line).join, index: trailing.index)
41
-
42
- @code_lines_dup[trailing.index] = joined_line
43
-
44
- @trailing_lines << joined_line
45
-
46
- lines.shift # Don't hide first trailing slash line
47
- lines.each(&:mark_invisible)
48
- end
49
-
50
- return @code_lines_dup
51
- end
52
- end
53
- end
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeadEnd
4
- # Determines what type of syntax error is in the source
5
- #
6
- # Example:
7
- #
8
- # puts WhoDisSyntaxError.new("def foo;").call.error_symbol
9
- # # => :missing_end
10
- class WhoDisSyntaxError < Ripper
11
- class Null
12
- def error_symbol; :missing_end; end
13
- def unmatched_symbol; :end ; end
14
- end
15
- attr_reader :error, :run_once
16
-
17
- # Return options:
18
- # - :missing_end
19
- # - :unmatched_syntax
20
- # - :unknown
21
- def error_symbol
22
- call
23
- @error_symbol
24
- end
25
-
26
- # Return options:
27
- # - :end
28
- # - :|
29
- # - :}
30
- # - :unknown
31
- def unmatched_symbol
32
- call
33
- @unmatched_symbol
34
- end
35
-
36
- def call
37
- @run_once ||= begin
38
- parse
39
- true
40
- end
41
- self
42
- end
43
-
44
- def on_parse_error(msg)
45
- return if @error_symbol && @unmatched_symbol
46
-
47
- @error = msg
48
- @unmatched_symbol = :unknown
49
-
50
- case @error
51
- when /unexpected end-of-input/
52
- @error_symbol = :missing_end
53
- when /expecting end-of-input/
54
- @unmatched_symbol = :end
55
- @error_symbol = :unmatched_syntax
56
- when /unexpected .* expecting '(?<unmatched_symbol>.*)'/
57
- @unmatched_symbol = $1.to_sym if $1
58
- @error_symbol = :unmatched_syntax
59
- when /unexpected `end'/, # Ruby 2.7 and 3.0
60
- /unexpected end/, # Ruby 2.6
61
- /unexpected keyword_end/i # Ruby 2.5
62
-
63
- @error_symbol = :unmatched_syntax
64
- else
65
- @error_symbol = :unknown
66
- end
67
- end
68
- end
69
- end