dead_end 3.0.3 → 3.1.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.
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