dead_end 2.0.0 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/dead_end.rb CHANGED
@@ -1,4 +1,159 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "dead_end/internals"
3
+ require_relative "dead_end/version"
4
+
5
+ require "tmpdir"
6
+ require "stringio"
7
+ require "pathname"
8
+ require "ripper"
9
+ require "timeout"
10
+
11
+ module DeadEnd
12
+ # Used to indicate a default value that cannot
13
+ # be confused with another input
14
+ DEFAULT_VALUE = Object.new.freeze
15
+
16
+ class Error < StandardError; end
17
+ TIMEOUT_DEFAULT = ENV.fetch("DEAD_END_TIMEOUT", 1).to_i
18
+
19
+ def self.handle_error(e)
20
+ filename = e.message.split(":").first
21
+ $stderr.sync = true
22
+
23
+ call(
24
+ source: Pathname(filename).read,
25
+ filename: filename
26
+ )
27
+
28
+ raise e
29
+ end
30
+
31
+ def self.record_dir(dir)
32
+ time = Time.now.strftime("%Y-%m-%d-%H-%M-%s-%N")
33
+ dir = Pathname(dir)
34
+ symlink = dir.join("last").tap { |path| path.delete if path.exist? }
35
+ dir.join(time).tap { |path|
36
+ path.mkpath
37
+ FileUtils.symlink(path.basename, symlink)
38
+ }
39
+ end
40
+
41
+ def self.call(source:, filename: DEFAULT_VALUE, terminal: DEFAULT_VALUE, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
42
+ search = nil
43
+ filename = nil if filename == DEFAULT_VALUE
44
+ Timeout.timeout(timeout) do
45
+ record_dir ||= ENV["DEBUG"] ? "tmp" : nil
46
+ search = CodeSearch.new(source, record_dir: record_dir).call
47
+ end
48
+
49
+ blocks = search.invalid_blocks
50
+ DisplayInvalidBlocks.new(
51
+ io: io,
52
+ blocks: blocks,
53
+ filename: filename,
54
+ terminal: terminal,
55
+ code_lines: search.code_lines
56
+ ).call
57
+ rescue Timeout::Error => e
58
+ io.puts "Search timed out DEAD_END_TIMEOUT=#{timeout}, run with DEBUG=1 for more info"
59
+ io.puts e.backtrace.first(3).join($/)
60
+ end
61
+
62
+ # Used for counting spaces
63
+ module SpaceCount
64
+ def self.indent(string)
65
+ string.split(/\S/).first&.length || 0
66
+ end
67
+ end
68
+
69
+ # This will tell you if the `code_lines` would be valid
70
+ # if you removed the `without_lines`. In short it's a
71
+ # way to detect if we've found the lines with syntax errors
72
+ # in our document yet.
73
+ #
74
+ # code_lines = [
75
+ # CodeLine.new(line: "def foo\n", index: 0)
76
+ # CodeLine.new(line: " def bar\n", index: 1)
77
+ # CodeLine.new(line: "end\n", index: 2)
78
+ # ]
79
+ #
80
+ # DeadEnd.valid_without?(
81
+ # without_lines: code_lines[1],
82
+ # code_lines: code_lines
83
+ # ) # => true
84
+ #
85
+ # DeadEnd.valid?(code_lines) # => false
86
+ def self.valid_without?(without_lines:, code_lines:)
87
+ lines = code_lines - Array(without_lines).flatten
88
+
89
+ if lines.empty?
90
+ true
91
+ else
92
+ valid?(lines)
93
+ end
94
+ end
95
+
96
+ def self.invalid?(source)
97
+ source = source.join if source.is_a?(Array)
98
+ source = source.to_s
99
+
100
+ Ripper.new(source).tap(&:parse).error?
101
+ end
102
+
103
+ # Returns truthy if a given input source is valid syntax
104
+ #
105
+ # DeadEnd.valid?(<<~EOM) # => true
106
+ # def foo
107
+ # end
108
+ # EOM
109
+ #
110
+ # DeadEnd.valid?(<<~EOM) # => false
111
+ # def foo
112
+ # def bar # Syntax error here
113
+ # end
114
+ # EOM
115
+ #
116
+ # You can also pass in an array of lines and they'll be
117
+ # joined before evaluating
118
+ #
119
+ # DeadEnd.valid?(
120
+ # [
121
+ # "def foo\n",
122
+ # "end\n"
123
+ # ]
124
+ # ) # => true
125
+ #
126
+ # DeadEnd.valid?(
127
+ # [
128
+ # "def foo\n",
129
+ # " def bar\n", # Syntax error here
130
+ # "end\n"
131
+ # ]
132
+ # ) # => false
133
+ #
134
+ # As an FYI the CodeLine class instances respond to `to_s`
135
+ # so passing a CodeLine in as an object or as an array
136
+ # will convert it to it's code representation.
137
+ def self.valid?(source)
138
+ !invalid?(source)
139
+ end
140
+ end
141
+
142
+ require_relative "dead_end/code_line"
143
+ require_relative "dead_end/code_block"
144
+ require_relative "dead_end/code_search"
145
+ require_relative "dead_end/code_frontier"
146
+ require_relative "dead_end/clean_document"
147
+
148
+ require_relative "dead_end/lex_all"
149
+ require_relative "dead_end/block_expand"
150
+ require_relative "dead_end/insertion_sort"
151
+ require_relative "dead_end/around_block_scan"
152
+ require_relative "dead_end/ripper_errors"
153
+ require_relative "dead_end/display_invalid_blocks"
154
+ require_relative "dead_end/parse_blocks_from_indent_line"
155
+
156
+ require_relative "dead_end/explain_syntax"
157
+
4
158
  require_relative "dead_end/auto"
159
+ require_relative "dead_end/cli"
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: 2.0.0
4
+ version: 3.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-10-11 00:00:00.000000000 Z
11
+ date: 2021-11-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
@@ -41,19 +41,21 @@ files:
41
41
  - lib/dead_end/block_expand.rb
42
42
  - lib/dead_end/capture_code_context.rb
43
43
  - lib/dead_end/clean_document.rb
44
+ - lib/dead_end/cli.rb
44
45
  - lib/dead_end/code_block.rb
45
46
  - lib/dead_end/code_frontier.rb
46
47
  - lib/dead_end/code_line.rb
47
48
  - lib/dead_end/code_search.rb
48
49
  - lib/dead_end/display_code_with_line_numbers.rb
49
50
  - lib/dead_end/display_invalid_blocks.rb
50
- - lib/dead_end/fyi.rb
51
- - lib/dead_end/internals.rb
51
+ - lib/dead_end/explain_syntax.rb
52
+ - lib/dead_end/insertion_sort.rb
53
+ - lib/dead_end/left_right_lex_count.rb
52
54
  - lib/dead_end/lex_all.rb
53
55
  - lib/dead_end/lex_value.rb
54
56
  - lib/dead_end/parse_blocks_from_indent_line.rb
57
+ - lib/dead_end/ripper_errors.rb
55
58
  - lib/dead_end/version.rb
56
- - lib/dead_end/who_dis_syntax_error.rb
57
59
  homepage: https://github.com/zombocom/dead_end.git
58
60
  licenses:
59
61
  - MIT
data/lib/dead_end/fyi.rb DELETED
@@ -1,8 +0,0 @@
1
- require_relative "../dead_end/internals"
2
-
3
- require_relative "auto"
4
-
5
- DeadEnd.send(:remove_const, :SEARCH_SOURCE_ON_ERROR_DEFAULT)
6
- DeadEnd::SEARCH_SOURCE_ON_ERROR_DEFAULT = false
7
-
8
- warn "DEPRECATED: calling `require 'dead_end/fyi'` is deprecated, `require 'dead_end'` instead"
@@ -1,154 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # This is the top level file, but is moved to `internals`
4
- # so the top level require can instead enable the "automatic" behavior
5
-
6
- require_relative "version"
7
-
8
- require "tmpdir"
9
- require "stringio"
10
- require "pathname"
11
- require "ripper"
12
- require "timeout"
13
-
14
- module DeadEnd
15
- class Error < StandardError; end
16
- SEARCH_SOURCE_ON_ERROR_DEFAULT = true
17
- TIMEOUT_DEFAULT = ENV.fetch("DEAD_END_TIMEOUT", 1).to_i
18
-
19
- def self.handle_error(e, search_source_on_error: SEARCH_SOURCE_ON_ERROR_DEFAULT)
20
- raise e unless e.message.include?("end-of-input")
21
-
22
- filename = e.message.split(":").first
23
-
24
- $stderr.sync = true
25
- warn "Run `$ dead_end #{filename}` for more options\n"
26
-
27
- if search_source_on_error
28
- call(
29
- source: Pathname(filename).read,
30
- filename: filename,
31
- terminal: true
32
- )
33
- end
34
-
35
- raise e
36
- end
37
-
38
- def self.call(source:, filename:, terminal: false, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
39
- search = nil
40
- Timeout.timeout(timeout) do
41
- record_dir ||= ENV["DEBUG"] ? "tmp" : nil
42
- search = CodeSearch.new(source, record_dir: record_dir).call
43
- end
44
-
45
- blocks = search.invalid_blocks
46
- DisplayInvalidBlocks.new(
47
- blocks: blocks,
48
- filename: filename,
49
- terminal: terminal,
50
- code_lines: search.code_lines,
51
- invalid_obj: invalid_type(source),
52
- io: io
53
- ).call
54
- rescue Timeout::Error => e
55
- io.puts "Search timed out DEAD_END_TIMEOUT=#{timeout}, run with DEBUG=1 for more info"
56
- io.puts e.backtrace.first(3).join($/)
57
- end
58
-
59
- # Used for counting spaces
60
- module SpaceCount
61
- def self.indent(string)
62
- string.split(/\S/).first&.length || 0
63
- end
64
- end
65
-
66
- # This will tell you if the `code_lines` would be valid
67
- # if you removed the `without_lines`. In short it's a
68
- # way to detect if we've found the lines with syntax errors
69
- # in our document yet.
70
- #
71
- # code_lines = [
72
- # CodeLine.new(line: "def foo\n", index: 0)
73
- # CodeLine.new(line: " def bar\n", index: 1)
74
- # CodeLine.new(line: "end\n", index: 2)
75
- # ]
76
- #
77
- # DeadEnd.valid_without?(
78
- # without_lines: code_lines[1],
79
- # code_lines: code_lines
80
- # ) # => true
81
- #
82
- # DeadEnd.valid?(code_lines) # => false
83
- def self.valid_without?(without_lines:, code_lines:)
84
- lines = code_lines - Array(without_lines).flatten
85
-
86
- if lines.empty?
87
- true
88
- else
89
- valid?(lines)
90
- end
91
- end
92
-
93
- def self.invalid?(source)
94
- source = source.join if source.is_a?(Array)
95
- source = source.to_s
96
-
97
- Ripper.new(source).tap(&:parse).error?
98
- end
99
-
100
- # Returns truthy if a given input source is valid syntax
101
- #
102
- # DeadEnd.valid?(<<~EOM) # => true
103
- # def foo
104
- # end
105
- # EOM
106
- #
107
- # DeadEnd.valid?(<<~EOM) # => false
108
- # def foo
109
- # def bar # Syntax error here
110
- # end
111
- # EOM
112
- #
113
- # You can also pass in an array of lines and they'll be
114
- # joined before evaluating
115
- #
116
- # DeadEnd.valid?(
117
- # [
118
- # "def foo\n",
119
- # "end\n"
120
- # ]
121
- # ) # => true
122
- #
123
- # DeadEnd.valid?(
124
- # [
125
- # "def foo\n",
126
- # " def bar\n", # Syntax error here
127
- # "end\n"
128
- # ]
129
- # ) # => false
130
- #
131
- # As an FYI the CodeLine class instances respond to `to_s`
132
- # so passing a CodeLine in as an object or as an array
133
- # will convert it to it's code representation.
134
- def self.valid?(source)
135
- !invalid?(source)
136
- end
137
-
138
- def self.invalid_type(source)
139
- WhoDisSyntaxError.new(source).call
140
- end
141
- end
142
-
143
- require_relative "code_line"
144
- require_relative "code_block"
145
- require_relative "code_search"
146
- require_relative "code_frontier"
147
- require_relative "clean_document"
148
-
149
- require_relative "lex_all"
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"
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeadEnd
4
- # Determines what type of syntax error that 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
13
- :missing_end
14
- end
15
-
16
- def unmatched_symbol
17
- :end
18
- end
19
- end
20
- attr_reader :error, :run_once
21
-
22
- # Return options:
23
- # - :missing_end
24
- # - :unmatched_syntax
25
- # - :unknown
26
- def error_symbol
27
- call
28
- @error_symbol
29
- end
30
-
31
- # Return options:
32
- # - :end
33
- # - :|
34
- # - :}
35
- # - :unknown
36
- def unmatched_symbol
37
- call
38
- @unmatched_symbol
39
- end
40
-
41
- def call
42
- @run_once ||= begin
43
- parse
44
- true
45
- end
46
- self
47
- end
48
-
49
- def on_parse_error(msg)
50
- return if @error_symbol && @unmatched_symbol
51
-
52
- @error = msg
53
- @unmatched_symbol = :unknown
54
-
55
- case @error
56
- when /unexpected end-of-input/
57
- @error_symbol = :missing_end
58
- when /expecting end-of-input/
59
- @unmatched_symbol = :end
60
- @error_symbol = :unmatched_syntax
61
- when /unexpected .* expecting '(?<unmatched_symbol>.*)'/
62
- @unmatched_symbol = $1.to_sym if $1
63
- @error_symbol = :unmatched_syntax
64
- when /unexpected `end'/, # Ruby 2.7 and 3.0
65
- /unexpected end/, # Ruby 2.6
66
- /unexpected keyword_end/i # Ruby 2.5
67
-
68
- @error_symbol = :unmatched_syntax
69
- else
70
- @error_symbol = :unknown
71
- end
72
- end
73
- end
74
- end