dead_end 3.0.3 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88cc140d421e1df16009993e7d7438054dad7a6b6fec740496e92c06c0bc2f6d
4
- data.tar.gz: ba736b1459f6c18aed62b566fa8e48ca6a4dd8379bc2dc2d99594c0daf136fb6
3
+ metadata.gz: 80d945bb6aff86e5dce0ef0998bb524e0b18b813412c21a2442fb5824b641ea9
4
+ data.tar.gz: 5cfa47620a0e21e5cf5de02c96a9d30da43aee18bace9ef386da54397518dedb
5
5
  SHA512:
6
- metadata.gz: 3cf585c9f073344d6259478c239fd45b77a5fb7fd1c37c18d3d2a7cf91323b00750cccb18d666935ea20ce2c209137216304429a26aac0bf26fec7aa5440ff14
7
- data.tar.gz: 8a818ac4001ce7ec01ae36c4c223f47c7686e111226718cf22e8e2ca9f00a0bb3f3ef6409ad703cb698a67dc62035225973f37ea5f527207c26e705acd4c8f33
6
+ metadata.gz: 94ada1bbdead52d89e883f18466d0bc0abc1fcb278e9ff4c191503392571f9ca00b33c71d6ed52ef83a50d7f7bf016b1f85ddbde0d0cb617b7201de680dac5be
7
+ data.tar.gz: 79eb132ca9d28196646823b00fd1b57feb9395fe88d4590272c809fd2af4074370853cc6e19ccd45fead55136586c5f50486329703eb138da178c5290139601b
data/.circleci/config.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  version: 2.1
2
2
  orbs:
3
- ruby: circleci/ruby@1.1.2
3
+ ruby: circleci/ruby@1.2.0
4
4
  references:
5
5
  unit: &unit
6
6
  run:
@@ -45,6 +45,17 @@ jobs:
45
45
  - ruby/install-deps
46
46
  - <<: *unit
47
47
 
48
+ "ruby-3-1":
49
+ docker:
50
+ - image: 'cimg/base:stable'
51
+ steps:
52
+ - checkout
53
+ - ruby/install:
54
+ version: '3.1.0-preview1'
55
+ - run: ruby -v
56
+ - ruby/install-deps
57
+ - <<: *unit
58
+
48
59
  "lint":
49
60
  docker:
50
61
  - image: circleci/ruby:3.0
@@ -61,4 +72,5 @@ workflows:
61
72
  - "ruby-2-6"
62
73
  - "ruby-2-7"
63
74
  - "ruby-3-0"
75
+ - "ruby-3-1"
64
76
  - "lint"
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## HEAD (unreleased)
2
2
 
3
+ ## 3.1.0
4
+
5
+ - Add support for Ruby 3.1 by updating `require_relative` logic (https://github.com/zombocom/dead_end/pull/120)
6
+ - Requiring `dead_end/auto` is now deprecated please require `dead_end` instead (https://github.com/zombocom/dead_end/pull/119)
7
+ - Requiring `dead_end/api` now loads code without monkeypatching core extensions (https://github.com/zombocom/dead_end/pull/119)
8
+ - The interface `DeadEnd.handle_error` is declared public and stable (https://github.com/zombocom/dead_end/pull/119)
9
+
3
10
  ## 3.0.3
4
11
 
5
12
  - Expand explanations coming from additional Ripper errors (https://github.com/zombocom/dead_end/pull/117)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dead_end (3.0.3)
4
+ dead_end (3.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -166,6 +166,16 @@ Here's an example:
166
166
 
167
167
  ![](assets/syntax_search.gif)
168
168
 
169
+ ## Use internals
170
+
171
+ To use the `dead_end` gem without monkeypatching you can `require 'dead_en/api'`. This will allow you to load `dead_end` and use its internals without mutating `require`.
172
+
173
+ Stable internal interface(s):
174
+
175
+ - `DeadEnd.handle_error(e)`
176
+
177
+ Any other entrypoints are subject to change without warning. If you want to use an internal interface from `dead_end` not on this list, open an issue to explain your use case.
178
+
169
179
  ## Development
170
180
 
171
181
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,196 @@
1
+ require_relative "version"
2
+
3
+ require "tmpdir"
4
+ require "stringio"
5
+ require "pathname"
6
+ require "ripper"
7
+ require "timeout"
8
+
9
+ module DeadEnd
10
+ # Used to indicate a default value that cannot
11
+ # be confused with another input.
12
+ DEFAULT_VALUE = Object.new.freeze
13
+
14
+ class Error < StandardError; end
15
+ TIMEOUT_DEFAULT = ENV.fetch("DEAD_END_TIMEOUT", 1).to_i
16
+
17
+ # DeadEnd.handle_error [Public]
18
+ #
19
+ # Takes a `SyntaxError`` exception, uses the
20
+ # error message to locate the file. Then the file
21
+ # will be analyzed to find the location of the syntax
22
+ # error and emit that location to stderr.
23
+ #
24
+ # Example:
25
+ #
26
+ # begin
27
+ # require 'bad_file'
28
+ # rescue => e
29
+ # DeadEnd.handle_error(e)
30
+ # end
31
+ #
32
+ # By default it will re-raise the exception unless
33
+ # `re_raise: false`. The message output location
34
+ # can be configured using the `io: $stderr` input.
35
+ #
36
+ # If a valid filename cannot be determined, the original
37
+ # exception will be re-raised (even with
38
+ # `re_raise: false`).
39
+ def self.handle_error(e, re_raise: true, io: $stderr)
40
+ unless e.is_a?(SyntaxError)
41
+ io.puts("DeadEnd: Must pass a SyntaxError, got: #{e.class}")
42
+ raise e
43
+ end
44
+
45
+ file = PathnameFromMessage.new(e.message, io: io).call.name
46
+ raise e unless file
47
+
48
+ io.sync = true
49
+
50
+ call(
51
+ io: io,
52
+ source: file.read,
53
+ filename: file
54
+ )
55
+
56
+ raise e if re_raise
57
+ end
58
+
59
+ # DeadEnd.call [Private]
60
+ #
61
+ # Main private interface
62
+ def self.call(source:, filename: DEFAULT_VALUE, terminal: DEFAULT_VALUE, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
63
+ search = nil
64
+ filename = nil if filename == DEFAULT_VALUE
65
+ Timeout.timeout(timeout) do
66
+ record_dir ||= ENV["DEBUG"] ? "tmp" : nil
67
+ search = CodeSearch.new(source, record_dir: record_dir).call
68
+ end
69
+
70
+ blocks = search.invalid_blocks
71
+ DisplayInvalidBlocks.new(
72
+ io: io,
73
+ blocks: blocks,
74
+ filename: filename,
75
+ terminal: terminal,
76
+ code_lines: search.code_lines
77
+ ).call
78
+ rescue Timeout::Error => e
79
+ io.puts "Search timed out DEAD_END_TIMEOUT=#{timeout}, run with DEBUG=1 for more info"
80
+ io.puts e.backtrace.first(3).join($/)
81
+ end
82
+
83
+ # DeadEnd.record_dir [Private]
84
+ #
85
+ # Used to generate a unique directory to record
86
+ # search steps for debugging
87
+ def self.record_dir(dir)
88
+ time = Time.now.strftime("%Y-%m-%d-%H-%M-%s-%N")
89
+ dir = Pathname(dir)
90
+ symlink = dir.join("last").tap { |path| path.delete if path.exist? }
91
+ dir.join(time).tap { |path|
92
+ path.mkpath
93
+ FileUtils.symlink(path.basename, symlink)
94
+ }
95
+ end
96
+
97
+ # DeadEnd.valid_without? [Private]
98
+ #
99
+ # This will tell you if the `code_lines` would be valid
100
+ # if you removed the `without_lines`. In short it's a
101
+ # way to detect if we've found the lines with syntax errors
102
+ # in our document yet.
103
+ #
104
+ # code_lines = [
105
+ # CodeLine.new(line: "def foo\n", index: 0)
106
+ # CodeLine.new(line: " def bar\n", index: 1)
107
+ # CodeLine.new(line: "end\n", index: 2)
108
+ # ]
109
+ #
110
+ # DeadEnd.valid_without?(
111
+ # without_lines: code_lines[1],
112
+ # code_lines: code_lines
113
+ # ) # => true
114
+ #
115
+ # DeadEnd.valid?(code_lines) # => false
116
+ def self.valid_without?(without_lines:, code_lines:)
117
+ lines = code_lines - Array(without_lines).flatten
118
+
119
+ if lines.empty?
120
+ true
121
+ else
122
+ valid?(lines)
123
+ end
124
+ end
125
+
126
+ # DeadEnd.invalid? [Private]
127
+ #
128
+ # Opposite of `DeadEnd.valid?`
129
+ def self.invalid?(source)
130
+ source = source.join if source.is_a?(Array)
131
+ source = source.to_s
132
+
133
+ Ripper.new(source).tap(&:parse).error?
134
+ end
135
+
136
+ # DeadEnd.valid? [Private]
137
+ #
138
+ # Returns truthy if a given input source is valid syntax
139
+ #
140
+ # DeadEnd.valid?(<<~EOM) # => true
141
+ # def foo
142
+ # end
143
+ # EOM
144
+ #
145
+ # DeadEnd.valid?(<<~EOM) # => false
146
+ # def foo
147
+ # def bar # Syntax error here
148
+ # end
149
+ # EOM
150
+ #
151
+ # You can also pass in an array of lines and they'll be
152
+ # joined before evaluating
153
+ #
154
+ # DeadEnd.valid?(
155
+ # [
156
+ # "def foo\n",
157
+ # "end\n"
158
+ # ]
159
+ # ) # => true
160
+ #
161
+ # DeadEnd.valid?(
162
+ # [
163
+ # "def foo\n",
164
+ # " def bar\n", # Syntax error here
165
+ # "end\n"
166
+ # ]
167
+ # ) # => false
168
+ #
169
+ # As an FYI the CodeLine class instances respond to `to_s`
170
+ # so passing a CodeLine in as an object or as an array
171
+ # will convert it to it's code representation.
172
+ def self.valid?(source)
173
+ !invalid?(source)
174
+ end
175
+ end
176
+
177
+ # Integration
178
+ require_relative "cli"
179
+
180
+ # Core logic
181
+ require_relative "code_search"
182
+ require_relative "code_frontier"
183
+ require_relative "explain_syntax"
184
+ require_relative "clean_document"
185
+
186
+ # Helpers
187
+ require_relative "lex_all"
188
+ require_relative "code_line"
189
+ require_relative "code_block"
190
+ require_relative "block_expand"
191
+ require_relative "ripper_errors"
192
+ require_relative "insertion_sort"
193
+ require_relative "around_block_scan"
194
+ require_relative "pathname_from_message"
195
+ require_relative "display_invalid_blocks"
196
+ require_relative "parse_blocks_from_indent_line"
data/lib/dead_end/auto.rb CHANGED
@@ -1,35 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../dead_end"
4
+ require_relative "core_ext"
4
5
 
5
- # Monkey patch kernel to ensure that all `require` calls call the same
6
- # method
7
- module Kernel
8
- module_function
9
-
10
- alias_method :dead_end_original_require, :require
11
- alias_method :dead_end_original_require_relative, :require_relative
12
- alias_method :dead_end_original_load, :load
13
-
14
- def load(file, wrap = false)
15
- dead_end_original_load(file)
16
- rescue SyntaxError => e
17
- DeadEnd.handle_error(e)
18
- end
19
-
20
- def require(file)
21
- dead_end_original_require(file)
22
- rescue SyntaxError => e
23
- DeadEnd.handle_error(e)
24
- end
25
-
26
- def require_relative(file)
27
- if Pathname.new(file).absolute?
28
- dead_end_original_require file
29
- else
30
- dead_end_original_require File.expand_path("../#{file}", Kernel.caller_locations(1, 1)[0].absolute_path)
31
- end
32
- rescue SyntaxError => e
33
- DeadEnd.handle_error(e)
34
- end
35
- end
6
+ warn "Calling `require 'dead_end/auto'` is deprecated, please `require 'dead_end'` instead."
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Monkey patch kernel to ensure that all `require` calls call the same
4
+ # method
5
+ module Kernel
6
+ module_function
7
+
8
+ alias_method :dead_end_original_require, :require
9
+ alias_method :dead_end_original_require_relative, :require_relative
10
+ alias_method :dead_end_original_load, :load
11
+
12
+ def load(file, wrap = false)
13
+ dead_end_original_load(file)
14
+ rescue SyntaxError => e
15
+ DeadEnd.handle_error(e)
16
+ end
17
+
18
+ def require(file)
19
+ dead_end_original_require(file)
20
+ rescue SyntaxError => e
21
+ DeadEnd.handle_error(e)
22
+ end
23
+
24
+ def require_relative(file)
25
+ if Pathname.new(file).absolute?
26
+ dead_end_original_require file
27
+ else
28
+ relative_from = caller_locations(1..1).first
29
+ relative_from_path = relative_from.absolute_path || relative_from.path
30
+ dead_end_original_require File.expand_path("../#{file}", relative_from_path)
31
+ end
32
+ rescue SyntaxError => e
33
+ DeadEnd.handle_error(e)
34
+ end
35
+ end
@@ -30,7 +30,7 @@ module DeadEnd
30
30
  end
31
31
 
32
32
  if @parts.empty?
33
- @io.puts "DeadEnd: could not find filename from #{@line.inspect}"
33
+ @io.puts "DeadEnd: Could not find filename from #{@line.inspect}"
34
34
  @name = nil
35
35
  end
36
36
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeadEnd
4
- VERSION = "3.0.3"
4
+ VERSION = "3.1.0"
5
5
  end
data/lib/dead_end.rb CHANGED
@@ -1,164 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
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
- file = PathnameFromMessage.new(e.message).call.name
21
- raise e unless file
22
-
23
- $stderr.sync = true
24
-
25
- call(
26
- source: file.read,
27
- filename: file
28
- )
29
-
30
- raise e
31
- end
32
-
33
- def self.record_dir(dir)
34
- time = Time.now.strftime("%Y-%m-%d-%H-%M-%s-%N")
35
- dir = Pathname(dir)
36
- symlink = dir.join("last").tap { |path| path.delete if path.exist? }
37
- dir.join(time).tap { |path|
38
- path.mkpath
39
- FileUtils.symlink(path.basename, symlink)
40
- }
41
- end
42
-
43
- def self.call(source:, filename: DEFAULT_VALUE, terminal: DEFAULT_VALUE, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
44
- search = nil
45
- filename = nil if filename == DEFAULT_VALUE
46
- Timeout.timeout(timeout) do
47
- record_dir ||= ENV["DEBUG"] ? "tmp" : nil
48
- search = CodeSearch.new(source, record_dir: record_dir).call
49
- end
50
-
51
- blocks = search.invalid_blocks
52
- DisplayInvalidBlocks.new(
53
- io: io,
54
- blocks: blocks,
55
- filename: filename,
56
- terminal: terminal,
57
- code_lines: search.code_lines
58
- ).call
59
- rescue Timeout::Error => e
60
- io.puts "Search timed out DEAD_END_TIMEOUT=#{timeout}, run with DEBUG=1 for more info"
61
- io.puts e.backtrace.first(3).join($/)
62
- end
63
-
64
- # Used for counting spaces
65
- module SpaceCount
66
- def self.indent(string)
67
- string.split(/\S/).first&.length || 0
68
- end
69
- end
70
-
71
- # This will tell you if the `code_lines` would be valid
72
- # if you removed the `without_lines`. In short it's a
73
- # way to detect if we've found the lines with syntax errors
74
- # in our document yet.
75
- #
76
- # code_lines = [
77
- # CodeLine.new(line: "def foo\n", index: 0)
78
- # CodeLine.new(line: " def bar\n", index: 1)
79
- # CodeLine.new(line: "end\n", index: 2)
80
- # ]
81
- #
82
- # DeadEnd.valid_without?(
83
- # without_lines: code_lines[1],
84
- # code_lines: code_lines
85
- # ) # => true
86
- #
87
- # DeadEnd.valid?(code_lines) # => false
88
- def self.valid_without?(without_lines:, code_lines:)
89
- lines = code_lines - Array(without_lines).flatten
90
-
91
- if lines.empty?
92
- true
93
- else
94
- valid?(lines)
95
- end
96
- end
97
-
98
- def self.invalid?(source)
99
- source = source.join if source.is_a?(Array)
100
- source = source.to_s
101
-
102
- Ripper.new(source).tap(&:parse).error?
103
- end
104
-
105
- # Returns truthy if a given input source is valid syntax
106
- #
107
- # DeadEnd.valid?(<<~EOM) # => true
108
- # def foo
109
- # end
110
- # EOM
111
- #
112
- # DeadEnd.valid?(<<~EOM) # => false
113
- # def foo
114
- # def bar # Syntax error here
115
- # end
116
- # EOM
117
- #
118
- # You can also pass in an array of lines and they'll be
119
- # joined before evaluating
120
- #
121
- # DeadEnd.valid?(
122
- # [
123
- # "def foo\n",
124
- # "end\n"
125
- # ]
126
- # ) # => true
127
- #
128
- # DeadEnd.valid?(
129
- # [
130
- # "def foo\n",
131
- # " def bar\n", # Syntax error here
132
- # "end\n"
133
- # ]
134
- # ) # => false
135
- #
136
- # As an FYI the CodeLine class instances respond to `to_s`
137
- # so passing a CodeLine in as an object or as an array
138
- # will convert it to it's code representation.
139
- def self.valid?(source)
140
- !invalid?(source)
141
- end
142
- end
143
-
144
- # Integration
145
- require_relative "dead_end/cli"
146
- require_relative "dead_end/auto"
147
-
148
- # Core logic
149
- require_relative "dead_end/code_search"
150
- require_relative "dead_end/code_frontier"
151
- require_relative "dead_end/explain_syntax"
152
- require_relative "dead_end/clean_document"
153
-
154
- # Helpers
155
- require_relative "dead_end/lex_all"
156
- require_relative "dead_end/code_line"
157
- require_relative "dead_end/code_block"
158
- require_relative "dead_end/block_expand"
159
- require_relative "dead_end/ripper_errors"
160
- require_relative "dead_end/insertion_sort"
161
- require_relative "dead_end/around_block_scan"
162
- require_relative "dead_end/pathname_from_message"
163
- require_relative "dead_end/display_invalid_blocks"
164
- require_relative "dead_end/parse_blocks_from_indent_line"
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: 3.0.3
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - schneems
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-17 00:00:00.000000000 Z
11
+ date: 2021-11-21 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
@@ -36,6 +36,7 @@ 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
@@ -46,6 +47,7 @@ files:
46
47
  - lib/dead_end/code_frontier.rb
47
48
  - lib/dead_end/code_line.rb
48
49
  - lib/dead_end/code_search.rb
50
+ - lib/dead_end/core_ext.rb
49
51
  - lib/dead_end/display_code_with_line_numbers.rb
50
52
  - lib/dead_end/display_invalid_blocks.rb
51
53
  - lib/dead_end/explain_syntax.rb
@@ -78,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
80
  - !ruby/object:Gem::Version
79
81
  version: '0'
80
82
  requirements: []
81
- rubygems_version: 3.2.22
83
+ rubygems_version: 3.3.0.dev
82
84
  signing_key:
83
85
  specification_version: 4
84
86
  summary: Find syntax errors in your source in a snap