dead_end 1.2.0 → 3.0.0

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.
@@ -1,158 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # This is the top level file, but is moved to `internals`
5
- # so the top level file can instead enable the "automatic" behavior
6
-
7
- require_relative "version"
8
-
9
- require "tmpdir"
10
- require "stringio"
11
- require "pathname"
12
- require "ripper"
13
- require "timeout"
14
-
15
- module DeadEnd
16
- class Error < StandardError; end
17
- SEARCH_SOURCE_ON_ERROR_DEFAULT = true
18
- TIMEOUT_DEFAULT = ENV.fetch("DEAD_END_TIMEOUT", 5).to_i
19
-
20
- def self.handle_error(e, search_source_on_error: SEARCH_SOURCE_ON_ERROR_DEFAULT)
21
- raise e unless e.message.include?("end-of-input")
22
-
23
- filename = e.message.split(":").first
24
-
25
- $stderr.sync = true
26
- warn "Run `$ dead_end #{filename}` for more options\n"
27
-
28
- if search_source_on_error
29
- call(
30
- source: Pathname(filename).read,
31
- filename: filename,
32
- terminal: true
33
- )
34
- end
35
-
36
- warn ""
37
- warn ""
38
- raise e
39
- end
40
-
41
- def self.call(source:, filename:, terminal: false, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
42
- search = nil
43
- Timeout.timeout(timeout) do
44
- record_dir ||= ENV["DEBUG"] ? "tmp" : nil
45
- search = CodeSearch.new(source, record_dir: record_dir).call
46
- end
47
-
48
- blocks = search.invalid_blocks
49
- DisplayInvalidBlocks.new(
50
- blocks: blocks,
51
- filename: filename,
52
- terminal: terminal,
53
- code_lines: search.code_lines,
54
- invalid_obj: invalid_type(source),
55
- io: io
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
-
141
- def self.invalid_type(source)
142
- WhoDisSyntaxError.new(source).call
143
- end
144
- end
145
-
146
- require_relative "code_line"
147
- require_relative "code_block"
148
- 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"
153
-
154
- require_relative "code_search"
155
- require_relative "who_dis_syntax_error"
156
- require_relative "heredoc_block_parse"
157
- require_relative "lex_all"
158
- require_relative "trailing_slash_join"
@@ -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..].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
- @code_lines_dup
51
- end
52
- end
53
- end
@@ -1,74 +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
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