dead_end 1.1.6 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "banner"
3
4
  require_relative "capture_code_context"
4
5
  require_relative "display_code_with_line_numbers"
5
6
 
@@ -8,7 +9,7 @@ module DeadEnd
8
9
  class DisplayInvalidBlocks
9
10
  attr_reader :filename
10
11
 
11
- def initialize(code_lines: ,blocks:, io: $stderr, filename: nil, terminal: false, invalid_obj: WhoDisSyntaxError::Null.new)
12
+ def initialize(code_lines:, blocks:, io: $stderr, filename: nil, terminal: false, invalid_obj: WhoDisSyntaxError::Null.new)
12
13
  @terminal = terminal
13
14
  @filename = filename
14
15
  @io = io
@@ -21,11 +22,15 @@ module DeadEnd
21
22
  @invalid_obj = invalid_obj
22
23
  end
23
24
 
25
+ def document_ok?
26
+ @blocks.none? { |b| !b.hidden? }
27
+ end
28
+
24
29
  def call
25
- if @blocks.any? { |b| !b.hidden? }
26
- found_invalid_blocks
27
- else
30
+ if document_ok?
28
31
  @io.puts "Syntax OK"
32
+ else
33
+ found_invalid_blocks
29
34
  end
30
35
  self
31
36
  end
@@ -37,8 +42,10 @@ module DeadEnd
37
42
 
38
43
  private def found_invalid_blocks
39
44
  @io.puts
40
- @io.puts banner
41
- @io.puts
45
+ if banner
46
+ @io.puts banner
47
+ @io.puts
48
+ end
42
49
  @io.puts("file: #{filename}") if filename
43
50
  @io.puts <<~EOM
44
51
  simplified:
@@ -48,52 +55,15 @@ module DeadEnd
48
55
  end
49
56
 
50
57
  def banner
51
- case @invalid_obj.error_symbol
52
- when :missing_end
53
- <<~EOM
54
- DeadEnd: Missing `end` detected
55
-
56
- This code has a missing `end`. Ensure that all
57
- syntax keywords (`def`, `do`, etc.) have a matching `end`.
58
- EOM
59
- when :unmatched_syntax
60
- case @invalid_obj.unmatched_symbol
61
- when :end
62
- <<~EOM
63
- DeadEnd: Unmatched `end` detected
64
-
65
- This code has an unmatched `end`. Ensure that all `end` lines
66
- in your code have a matching syntax keyword (`def`, `do`, etc.)
67
- and that you don't have any extra `end` lines.
68
- EOM
69
- when :|
70
- <<~EOM
71
- DeadEnd: Unmatched `|` character detected
72
-
73
- Example:
74
-
75
- `do |x` should be `do |x|`
76
- EOM
77
- when :"}"
78
- <<~EOM
79
- DeadEnd: Unmatched `}` character detected
80
-
81
- This code has an unmatched `}`. Ensure that opening curl braces are
82
- closed: `{ }`.
83
- EOM
84
- else
85
- "DeadEnd: Unmatched `#{@invalid_obj.unmatched_symbol}` detected"
86
- end
87
- end
88
-
58
+ Banner.new(invalid_obj: @invalid_obj).call
89
59
  end
90
60
 
91
61
  def indent(string, with: " ")
92
- string.each_line.map {|l| with + l }.join
62
+ string.each_line.map { |l| with + l }.join
93
63
  end
94
64
 
95
65
  def code_block
96
- string = String.new("")
66
+ string = +""
97
67
  string << code_with_context
98
68
  string
99
69
  end
@@ -107,7 +77,7 @@ module DeadEnd
107
77
  DisplayCodeWithLineNumbers.new(
108
78
  lines: lines,
109
79
  terminal: @terminal,
110
- highlight_lines: @invalid_lines,
80
+ highlight_lines: @invalid_lines
111
81
  ).call
112
82
  end
113
83
 
@@ -115,7 +85,7 @@ module DeadEnd
115
85
  DisplayCodeWithLineNumbers.new(
116
86
  lines: @code_lines.select(&:visible?),
117
87
  terminal: @terminal,
118
- highlight_lines: @invalid_lines,
88
+ highlight_lines: @invalid_lines
119
89
  ).call
120
90
  end
121
91
  end
data/lib/dead_end/fyi.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  require_relative "../dead_end/internals"
2
2
 
3
- require_relative "auto.rb"
3
+ require_relative "auto"
4
4
 
5
5
  DeadEnd.send(:remove_const, :SEARCH_SOURCE_ON_ERROR_DEFAULT)
6
6
  DeadEnd::SEARCH_SOURCE_ON_ERROR_DEFAULT = false
7
7
 
8
+ warn "DEPRECATED: calling `require 'dead_end/fyi'` is deprecated, `require 'dead_end'` instead"
@@ -1,43 +1,41 @@
1
1
  # frozen_string_literal: true
2
- #
2
+
3
3
  # This is the top level file, but is moved to `internals`
4
- # so the top level file can instead enable the "automatic" behavior
4
+ # so the top level require can instead enable the "automatic" behavior
5
5
 
6
6
  require_relative "version"
7
7
 
8
- require 'tmpdir'
9
- require 'stringio'
10
- require 'pathname'
11
- require 'ripper'
12
- require 'timeout'
8
+ require "tmpdir"
9
+ require "stringio"
10
+ require "pathname"
11
+ require "ripper"
12
+ require "timeout"
13
13
 
14
14
  module DeadEnd
15
15
  class Error < StandardError; end
16
16
  SEARCH_SOURCE_ON_ERROR_DEFAULT = true
17
- TIMEOUT_DEFAULT = ENV.fetch("DEAD_END_TIMEOUT", 5).to_i
17
+ TIMEOUT_DEFAULT = ENV.fetch("DEAD_END_TIMEOUT", 1).to_i
18
18
 
19
19
  def self.handle_error(e, search_source_on_error: SEARCH_SOURCE_ON_ERROR_DEFAULT)
20
- raise e if !e.message.include?("end-of-input")
20
+ raise e unless e.message.include?("end-of-input")
21
21
 
22
22
  filename = e.message.split(":").first
23
23
 
24
24
  $stderr.sync = true
25
- $stderr.puts "Run `$ dead_end #{filename}` for more options\n"
25
+ warn "Run `$ dead_end #{filename}` for more options\n"
26
26
 
27
27
  if search_source_on_error
28
- self.call(
28
+ call(
29
29
  source: Pathname(filename).read,
30
30
  filename: filename,
31
- terminal: true,
31
+ terminal: true
32
32
  )
33
33
  end
34
34
 
35
- $stderr.puts ""
36
- $stderr.puts ""
37
35
  raise e
38
36
  end
39
37
 
40
- def self.call(source: , filename: , terminal: false, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
38
+ def self.call(source:, filename:, terminal: false, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
41
39
  search = nil
42
40
  Timeout.timeout(timeout) do
43
41
  record_dir ||= ENV["DEBUG"] ? "tmp" : nil
@@ -82,13 +80,13 @@ module DeadEnd
82
80
  # ) # => true
83
81
  #
84
82
  # DeadEnd.valid?(code_lines) # => false
85
- def self.valid_without?(without_lines: , code_lines:)
83
+ def self.valid_without?(without_lines:, code_lines:)
86
84
  lines = code_lines - Array(without_lines).flatten
87
85
 
88
86
  if lines.empty?
89
- return true
87
+ true
90
88
  else
91
- return valid?(lines)
89
+ valid?(lines)
92
90
  end
93
91
  end
94
92
 
@@ -137,7 +135,6 @@ module DeadEnd
137
135
  !invalid?(source)
138
136
  end
139
137
 
140
-
141
138
  def self.invalid_type(source)
142
139
  WhoDisSyntaxError.new(source).call
143
140
  end
@@ -145,14 +142,13 @@ end
145
142
 
146
143
  require_relative "code_line"
147
144
  require_relative "code_block"
145
+ require_relative "code_search"
148
146
  require_relative "code_frontier"
149
- require_relative "display_invalid_blocks"
150
- require_relative "around_block_scan"
151
- require_relative "block_expand"
152
- require_relative "parse_blocks_from_indent_line"
147
+ require_relative "clean_document"
153
148
 
154
- require_relative "code_search"
155
- require_relative "who_dis_syntax_error"
156
- require_relative "heredoc_block_parse"
157
149
  require_relative "lex_all"
158
- require_relative "trailing_slash_join"
150
+ require_relative "block_expand"
151
+ require_relative "around_block_scan"
152
+ require_relative "who_dis_syntax_error"
153
+ require_relative "display_invalid_blocks"
154
+ require_relative "parse_blocks_from_indent_line"
@@ -8,20 +8,24 @@ module DeadEnd
8
8
  class LexAll
9
9
  include Enumerable
10
10
 
11
- def initialize(source: )
11
+ def initialize(source:)
12
12
  @lex = Ripper.lex(source)
13
- lineno = @lex.last&.first&.first + 1
13
+ lineno = @lex.last.first.first + 1
14
14
  source_lines = source.lines
15
15
  last_lineno = source_lines.count
16
16
 
17
17
  until lineno >= last_lineno
18
18
  lines = source_lines[lineno..-1]
19
19
 
20
- @lex.concat(Ripper.lex(lines.join, '-', lineno + 1))
21
- lineno = @lex.last&.first&.first + 1
20
+ @lex.concat(Ripper.lex(lines.join, "-", lineno + 1))
21
+ lineno = @lex.last.first.first + 1
22
22
  end
23
23
 
24
- @lex.map! {|(line, _), type, token, state| LexValue.new(line, _, type, token, state) }
24
+ @lex.map! { |(line, _), type, token, state| LexValue.new(line, type, token, state) }
25
+ end
26
+
27
+ def to_a
28
+ @lex
25
29
  end
26
30
 
27
31
  def each
@@ -31,34 +35,14 @@ module DeadEnd
31
35
  end
32
36
  end
33
37
 
34
- def last
35
- @lex.last
38
+ def [](index)
39
+ @lex[index]
36
40
  end
37
41
 
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
42
+ def last
43
+ @lex.last
62
44
  end
63
45
  end
64
46
  end
47
+
48
+ require_relative "lex_value"
@@ -0,0 +1,62 @@
1
+ module DeadEnd
2
+ # Value object for accessing lex values
3
+ #
4
+ # This lex:
5
+ #
6
+ # [1, 0], :on_ident, "describe", CMDARG
7
+ #
8
+ # Would translate into:
9
+ #
10
+ # lex.line # => 1
11
+ # lex.type # => :on_indent
12
+ # lex.token # => "describe"
13
+ class LexValue
14
+ attr_reader :line, :type, :token, :state
15
+
16
+ def initialize(line, type, token, state)
17
+ @line = line
18
+ @type = type
19
+ @token = token
20
+ @state = state
21
+
22
+ set_kw_end
23
+ end
24
+
25
+ private def set_kw_end
26
+ @is_end = false
27
+ @is_kw = false
28
+ return if type != :on_kw
29
+
30
+ case token
31
+ when "if", "unless", "while", "until"
32
+ # Only count if/unless when it's not a "trailing" if/unless
33
+ # https://github.com/ruby/ruby/blob/06b44f819eb7b5ede1ff69cecb25682b56a1d60c/lib/irb/ruby-lex.rb#L374-L375
34
+ @is_kw = true unless expr_label?
35
+ when "def", "case", "for", "begin", "class", "module", "do"
36
+ @is_kw = true
37
+ when "end"
38
+ @is_end = true
39
+ end
40
+ end
41
+
42
+ def ignore_newline?
43
+ type == :on_ignored_nl
44
+ end
45
+
46
+ def is_end?
47
+ @is_end
48
+ end
49
+
50
+ def is_kw?
51
+ @is_kw
52
+ end
53
+
54
+ def expr_beg?
55
+ state.anybits?(Ripper::EXPR_BEG)
56
+ end
57
+
58
+ def expr_label?
59
+ state.allbits?(Ripper::EXPR_LABEL)
60
+ end
61
+ end
62
+ 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
-
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeadEnd
4
- VERSION = "1.1.6"
4
+ VERSION = "2.0.1"
5
5
  end
@@ -1,16 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeadEnd
4
- # Determines what type of syntax error is in the source
4
+ # Determines what type of syntax error that is in the source
5
5
  #
6
6
  # Example:
7
7
  #
8
8
  # puts WhoDisSyntaxError.new("def foo;").call.error_symbol
9
9
  # # => :missing_end
10
10
  class WhoDisSyntaxError < Ripper
11
+ CHARACTERS = {"{": :"}", "}": :"{", "[": :"]", "]": :"[", "(": :")", ")": :"("}
11
12
  class Null
12
- def error_symbol; :missing_end; end
13
- def unmatched_symbol; :end ; end
13
+ def error_symbol
14
+ :missing_end
15
+ end
16
+
17
+ def unmatched_symbol
18
+ :end
19
+ end
14
20
  end
15
21
  attr_reader :error, :run_once
16
22
 
@@ -53,12 +59,20 @@ module DeadEnd
53
59
  when /expecting end-of-input/
54
60
  @unmatched_symbol = :end
55
61
  @error_symbol = :unmatched_syntax
56
- when /unexpected .* expecting '(?<unmatched_symbol>.*)'/
57
- @unmatched_symbol = $1.to_sym if $1
62
+ when /unexpected .* expecting ['`]?(?<unmatched_symbol>[^']*)/
63
+ if $1
64
+ character = $1.to_sym
65
+ @unmatched_symbol = CHARACTERS[character] || character
66
+ @unmatched_symbol = :end if @unmatched_symbol == :keyword_end
67
+ end
68
+ @error_symbol = :unmatched_syntax
69
+ when /unexpected '(?<unmatched_symbol>.*)'/
70
+ @unmatched_symbol = $1.to_sym
71
+ @unmatched_symbol = :end if @unmatched_symbol == :keyword_end
58
72
  @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
73
+ when /unexpected `end'/, # Ruby 2.7 and 3.0
74
+ /unexpected end/, # Ruby 2.6
75
+ /unexpected keyword_end/i # Ruby 2.5
62
76
 
63
77
  @error_symbol = :unmatched_syntax
64
78
  else
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.6
4
+ version: 2.0.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-02-25 00:00:00.000000000 Z
11
+ date: 2021-10-19 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
@@ -38,8 +38,10 @@ files:
38
38
  - lib/dead_end.rb
39
39
  - lib/dead_end/around_block_scan.rb
40
40
  - lib/dead_end/auto.rb
41
+ - lib/dead_end/banner.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
43
45
  - lib/dead_end/code_block.rb
44
46
  - lib/dead_end/code_frontier.rb
45
47
  - lib/dead_end/code_line.rb
@@ -47,11 +49,10 @@ files:
47
49
  - lib/dead_end/display_code_with_line_numbers.rb
48
50
  - lib/dead_end/display_invalid_blocks.rb
49
51
  - lib/dead_end/fyi.rb
50
- - lib/dead_end/heredoc_block_parse.rb
51
52
  - lib/dead_end/internals.rb
52
53
  - lib/dead_end/lex_all.rb
54
+ - lib/dead_end/lex_value.rb
53
55
  - lib/dead_end/parse_blocks_from_indent_line.rb
54
- - lib/dead_end/trailing_slash_join.rb
55
56
  - lib/dead_end/version.rb
56
57
  - lib/dead_end/who_dis_syntax_error.rb
57
58
  homepage: https://github.com/zombocom/dead_end.git
@@ -75,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
76
  - !ruby/object:Gem::Version
76
77
  version: '0'
77
78
  requirements: []
78
- rubygems_version: 3.2.3
79
+ rubygems_version: 3.2.22
79
80
  signing_key:
80
81
  specification_version: 4
81
82
  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
@@ -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