dead_end 1.2.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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