dead_end 1.1.7 → 3.1.1
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 +4 -4
- data/.circleci/config.yml +27 -1
- data/.github/workflows/check_changelog.yml +14 -7
- data/.standard.yml +1 -0
- data/CHANGELOG.md +60 -0
- data/CODE_OF_CONDUCT.md +2 -2
- data/Gemfile +2 -0
- data/Gemfile.lock +31 -2
- data/README.md +122 -35
- data/Rakefile +1 -1
- data/dead_end.gemspec +12 -12
- data/exe/dead_end +4 -67
- data/lib/dead_end/{internals.rb → api.rb} +90 -52
- data/lib/dead_end/around_block_scan.rb +16 -18
- data/lib/dead_end/auto.rb +3 -101
- data/lib/dead_end/block_expand.rb +6 -5
- data/lib/dead_end/capture_code_context.rb +167 -50
- data/lib/dead_end/clean_document.rb +304 -0
- data/lib/dead_end/cli.rb +129 -0
- data/lib/dead_end/code_block.rb +20 -4
- data/lib/dead_end/code_frontier.rb +74 -29
- data/lib/dead_end/code_line.rb +176 -87
- data/lib/dead_end/code_search.rb +40 -51
- data/lib/dead_end/core_ext.rb +35 -0
- data/lib/dead_end/display_code_with_line_numbers.rb +7 -8
- data/lib/dead_end/display_invalid_blocks.rb +42 -80
- data/lib/dead_end/explain_syntax.rb +103 -0
- data/lib/dead_end/insertion_sort.rb +46 -0
- data/lib/dead_end/left_right_lex_count.rb +168 -0
- data/lib/dead_end/lex_all.rb +25 -34
- data/lib/dead_end/lex_value.rb +70 -0
- data/lib/dead_end/parse_blocks_from_indent_line.rb +3 -4
- data/lib/dead_end/pathname_from_message.rb +47 -0
- data/lib/dead_end/ripper_errors.rb +36 -0
- data/lib/dead_end/version.rb +1 -1
- data/lib/dead_end.rb +2 -2
- metadata +14 -9
- data/.travis.yml +0 -6
- data/lib/dead_end/fyi.rb +0 -7
- data/lib/dead_end/heredoc_block_parse.rb +0 -30
- data/lib/dead_end/trailing_slash_join.rb +0 -53
- data/lib/dead_end/who_dis_syntax_error.rb +0 -69
data/lib/dead_end/lex_all.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DeadEnd
|
2
4
|
# Ripper.lex is not guaranteed to lex the entire source document
|
3
5
|
#
|
@@ -8,20 +10,29 @@ module DeadEnd
|
|
8
10
|
class LexAll
|
9
11
|
include Enumerable
|
10
12
|
|
11
|
-
def initialize(source: )
|
12
|
-
@lex = Ripper.
|
13
|
-
lineno = @lex.last
|
14
|
-
source_lines
|
15
|
-
last_lineno = source_lines.
|
13
|
+
def initialize(source:, source_lines: nil)
|
14
|
+
@lex = Ripper::Lexer.new(source, "-", 1).parse.sort_by(&:pos)
|
15
|
+
lineno = @lex.last.pos.first + 1
|
16
|
+
source_lines ||= source.lines
|
17
|
+
last_lineno = source_lines.length
|
16
18
|
|
17
19
|
until lineno >= last_lineno
|
18
20
|
lines = source_lines[lineno..-1]
|
19
21
|
|
20
|
-
@lex.concat(
|
21
|
-
|
22
|
+
@lex.concat(
|
23
|
+
Ripper::Lexer.new(lines.join, "-", lineno + 1).parse.sort_by(&:pos)
|
24
|
+
)
|
25
|
+
lineno = @lex.last.pos.first + 1
|
22
26
|
end
|
23
27
|
|
24
|
-
|
28
|
+
last_lex = nil
|
29
|
+
@lex.map! { |elem|
|
30
|
+
last_lex = LexValue.new(elem.pos.first, elem.event, elem.tok, elem.state, last_lex)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_a
|
35
|
+
@lex
|
25
36
|
end
|
26
37
|
|
27
38
|
def each
|
@@ -31,34 +42,14 @@ module DeadEnd
|
|
31
42
|
end
|
32
43
|
end
|
33
44
|
|
34
|
-
def
|
35
|
-
@lex
|
45
|
+
def [](index)
|
46
|
+
@lex[index]
|
36
47
|
end
|
37
48
|
|
38
|
-
|
39
|
-
|
40
|
-
# This lex:
|
41
|
-
#
|
42
|
-
# [1, 0], :on_ident, "describe", CMDARG
|
43
|
-
#
|
44
|
-
# Would translate into:
|
45
|
-
#
|
46
|
-
# lex.line # => 1
|
47
|
-
# lex.type # => :on_indent
|
48
|
-
# lex.token # => "describe"
|
49
|
-
class LexValue
|
50
|
-
attr_reader :line, :type, :token, :state
|
51
|
-
|
52
|
-
def initialize(line, _, type, token, state)
|
53
|
-
@line = line
|
54
|
-
@type = type
|
55
|
-
@token = token
|
56
|
-
@state = state
|
57
|
-
end
|
58
|
-
|
59
|
-
def expr_label?
|
60
|
-
state.allbits?(Ripper::EXPR_LABEL)
|
61
|
-
end
|
49
|
+
def last
|
50
|
+
@lex.last
|
62
51
|
end
|
63
52
|
end
|
64
53
|
end
|
54
|
+
|
55
|
+
require_relative "lex_value"
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeadEnd
|
4
|
+
# Value object for accessing lex values
|
5
|
+
#
|
6
|
+
# This lex:
|
7
|
+
#
|
8
|
+
# [1, 0], :on_ident, "describe", CMDARG
|
9
|
+
#
|
10
|
+
# Would translate into:
|
11
|
+
#
|
12
|
+
# lex.line # => 1
|
13
|
+
# lex.type # => :on_indent
|
14
|
+
# lex.token # => "describe"
|
15
|
+
class LexValue
|
16
|
+
attr_reader :line, :type, :token, :state
|
17
|
+
|
18
|
+
def initialize(line, type, token, state, last_lex = nil)
|
19
|
+
@line = line
|
20
|
+
@type = type
|
21
|
+
@token = token
|
22
|
+
@state = state
|
23
|
+
|
24
|
+
set_kw_end(last_lex)
|
25
|
+
end
|
26
|
+
|
27
|
+
private def set_kw_end(last_lex)
|
28
|
+
@is_end = false
|
29
|
+
@is_kw = false
|
30
|
+
return if type != :on_kw
|
31
|
+
#
|
32
|
+
return if last_lex && last_lex.fname? # https://github.com/ruby/ruby/commit/776759e300e4659bb7468e2b97c8c2d4359a2953
|
33
|
+
|
34
|
+
case token
|
35
|
+
when "if", "unless", "while", "until"
|
36
|
+
# Only count if/unless when it's not a "trailing" if/unless
|
37
|
+
# https://github.com/ruby/ruby/blob/06b44f819eb7b5ede1ff69cecb25682b56a1d60c/lib/irb/ruby-lex.rb#L374-L375
|
38
|
+
@is_kw = true unless expr_label?
|
39
|
+
when "def", "case", "for", "begin", "class", "module", "do"
|
40
|
+
@is_kw = true
|
41
|
+
when "end"
|
42
|
+
@is_end = true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def fname?
|
47
|
+
state.allbits?(Ripper::EXPR_FNAME)
|
48
|
+
end
|
49
|
+
|
50
|
+
def ignore_newline?
|
51
|
+
type == :on_ignored_nl
|
52
|
+
end
|
53
|
+
|
54
|
+
def is_end?
|
55
|
+
@is_end
|
56
|
+
end
|
57
|
+
|
58
|
+
def is_kw?
|
59
|
+
@is_kw
|
60
|
+
end
|
61
|
+
|
62
|
+
def expr_beg?
|
63
|
+
state.anybits?(Ripper::EXPR_BEG)
|
64
|
+
end
|
65
|
+
|
66
|
+
def expr_label?
|
67
|
+
state.allbits?(Ripper::EXPR_LABEL)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -4,7 +4,7 @@ module DeadEnd
|
|
4
4
|
# This class is responsible for generating initial code blocks
|
5
5
|
# that will then later be expanded.
|
6
6
|
#
|
7
|
-
# The biggest concern when guessing
|
7
|
+
# The biggest concern when guessing code blocks, is accidentally
|
8
8
|
# grabbing one that contains only an "end". In this example:
|
9
9
|
#
|
10
10
|
# def dog
|
@@ -29,7 +29,7 @@ module DeadEnd
|
|
29
29
|
class ParseBlocksFromIndentLine
|
30
30
|
attr_reader :code_lines
|
31
31
|
|
32
|
-
def initialize(code_lines:
|
32
|
+
def initialize(code_lines:)
|
33
33
|
@code_lines = code_lines
|
34
34
|
end
|
35
35
|
|
@@ -38,7 +38,7 @@ module DeadEnd
|
|
38
38
|
scan = AroundBlockScan.new(code_lines: code_lines, block: CodeBlock.new(lines: target_line))
|
39
39
|
.skip(:empty?)
|
40
40
|
.skip(:hidden?)
|
41
|
-
.scan_while {|line| line.indent >= target_line.indent }
|
41
|
+
.scan_while { |line| line.indent >= target_line.indent }
|
42
42
|
|
43
43
|
neighbors = scan.code_block.lines
|
44
44
|
|
@@ -53,4 +53,3 @@ module DeadEnd
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
56
|
-
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeadEnd
|
4
|
+
# Converts a SyntaxError message to a path
|
5
|
+
#
|
6
|
+
# Handles the case where the filename has a colon in it
|
7
|
+
# such as on a windows file system: https://github.com/zombocom/dead_end/issues/111
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# message = "/tmp/scratch:2:in `require_relative': /private/tmp/bad.rb:1: syntax error, unexpected `end' (SyntaxError)"
|
12
|
+
# puts PathnameFromMessage.new(message).call.name
|
13
|
+
# # => "/tmp/scratch.rb"
|
14
|
+
#
|
15
|
+
class PathnameFromMessage
|
16
|
+
attr_reader :name
|
17
|
+
|
18
|
+
def initialize(message, io: $stderr)
|
19
|
+
@line = message.lines.first
|
20
|
+
@parts = @line.split(":")
|
21
|
+
@guess = []
|
22
|
+
@name = nil
|
23
|
+
@io = io
|
24
|
+
end
|
25
|
+
|
26
|
+
def call
|
27
|
+
until stop?
|
28
|
+
@guess << @parts.shift
|
29
|
+
@name = Pathname(@guess.join(":"))
|
30
|
+
end
|
31
|
+
|
32
|
+
if @parts.empty?
|
33
|
+
@io.puts "DeadEnd: Could not find filename from #{@line.inspect}"
|
34
|
+
@name = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def stop?
|
41
|
+
return true if @parts.empty?
|
42
|
+
return false if @guess.empty?
|
43
|
+
|
44
|
+
@name&.exist?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeadEnd
|
4
|
+
# Capture parse errors from ripper
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# puts RipperErrors.new(" def foo").call.errors
|
9
|
+
# # => ["syntax error, unexpected end-of-input, expecting ';' or '\\n'"]
|
10
|
+
class RipperErrors < Ripper
|
11
|
+
attr_reader :errors
|
12
|
+
|
13
|
+
# Comes from ripper, called
|
14
|
+
# on every parse error, msg
|
15
|
+
# is a string
|
16
|
+
def on_parse_error(msg)
|
17
|
+
@errors ||= []
|
18
|
+
@errors << msg
|
19
|
+
end
|
20
|
+
|
21
|
+
alias_method :on_alias_error, :on_parse_error
|
22
|
+
alias_method :on_assign_error, :on_parse_error
|
23
|
+
alias_method :on_class_name_error, :on_parse_error
|
24
|
+
alias_method :on_param_error, :on_parse_error
|
25
|
+
alias_method :compile_error, :on_parse_error
|
26
|
+
|
27
|
+
def call
|
28
|
+
@run_once ||= begin
|
29
|
+
@errors = []
|
30
|
+
parse
|
31
|
+
true
|
32
|
+
end
|
33
|
+
self
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/dead_end/version.rb
CHANGED
data/lib/dead_end.rb
CHANGED
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: 1.1
|
4
|
+
version: 3.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- schneems
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-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
|
@@ -23,7 +23,7 @@ files:
|
|
23
23
|
- ".github/workflows/check_changelog.yml"
|
24
24
|
- ".gitignore"
|
25
25
|
- ".rspec"
|
26
|
-
- ".
|
26
|
+
- ".standard.yml"
|
27
27
|
- CHANGELOG.md
|
28
28
|
- CODE_OF_CONDUCT.md
|
29
29
|
- Gemfile
|
@@ -36,24 +36,29 @@ 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
|
42
43
|
- lib/dead_end/capture_code_context.rb
|
44
|
+
- lib/dead_end/clean_document.rb
|
45
|
+
- lib/dead_end/cli.rb
|
43
46
|
- lib/dead_end/code_block.rb
|
44
47
|
- lib/dead_end/code_frontier.rb
|
45
48
|
- lib/dead_end/code_line.rb
|
46
49
|
- lib/dead_end/code_search.rb
|
50
|
+
- lib/dead_end/core_ext.rb
|
47
51
|
- lib/dead_end/display_code_with_line_numbers.rb
|
48
52
|
- lib/dead_end/display_invalid_blocks.rb
|
49
|
-
- lib/dead_end/
|
50
|
-
- lib/dead_end/
|
51
|
-
- lib/dead_end/
|
53
|
+
- lib/dead_end/explain_syntax.rb
|
54
|
+
- lib/dead_end/insertion_sort.rb
|
55
|
+
- lib/dead_end/left_right_lex_count.rb
|
52
56
|
- lib/dead_end/lex_all.rb
|
57
|
+
- lib/dead_end/lex_value.rb
|
53
58
|
- lib/dead_end/parse_blocks_from_indent_line.rb
|
54
|
-
- lib/dead_end/
|
59
|
+
- lib/dead_end/pathname_from_message.rb
|
60
|
+
- lib/dead_end/ripper_errors.rb
|
55
61
|
- lib/dead_end/version.rb
|
56
|
-
- lib/dead_end/who_dis_syntax_error.rb
|
57
62
|
homepage: https://github.com/zombocom/dead_end.git
|
58
63
|
licenses:
|
59
64
|
- MIT
|
@@ -75,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
80
|
- !ruby/object:Gem::Version
|
76
81
|
version: '0'
|
77
82
|
requirements: []
|
78
|
-
rubygems_version: 3.2.
|
83
|
+
rubygems_version: 3.2.32
|
79
84
|
signing_key:
|
80
85
|
specification_version: 4
|
81
86
|
summary: Find syntax errors in your source in a snap
|
data/.travis.yml
DELETED
data/lib/dead_end/fyi.rb
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module DeadEnd
|
4
|
-
# Takes in a source, and returns blocks containing each heredoc
|
5
|
-
class HeredocBlockParse
|
6
|
-
private; attr_reader :code_lines, :lex; public
|
7
|
-
|
8
|
-
def initialize(source:, code_lines: )
|
9
|
-
@code_lines = code_lines
|
10
|
-
@lex = LexAll.new(source: source)
|
11
|
-
end
|
12
|
-
|
13
|
-
def call
|
14
|
-
blocks = []
|
15
|
-
beginning = []
|
16
|
-
@lex.each do |lex|
|
17
|
-
case lex.type
|
18
|
-
when :on_heredoc_beg
|
19
|
-
beginning << lex.line
|
20
|
-
when :on_heredoc_end
|
21
|
-
start_index = beginning.pop - 1
|
22
|
-
end_index = lex.line - 1
|
23
|
-
blocks << CodeBlock.new(lines: code_lines[start_index..end_index])
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
blocks
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -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..-1].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
|
-
return @code_lines_dup
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
@@ -1,69 +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; :missing_end; end
|
13
|
-
def unmatched_symbol; :end ; end
|
14
|
-
end
|
15
|
-
attr_reader :error, :run_once
|
16
|
-
|
17
|
-
# Return options:
|
18
|
-
# - :missing_end
|
19
|
-
# - :unmatched_syntax
|
20
|
-
# - :unknown
|
21
|
-
def error_symbol
|
22
|
-
call
|
23
|
-
@error_symbol
|
24
|
-
end
|
25
|
-
|
26
|
-
# Return options:
|
27
|
-
# - :end
|
28
|
-
# - :|
|
29
|
-
# - :}
|
30
|
-
# - :unknown
|
31
|
-
def unmatched_symbol
|
32
|
-
call
|
33
|
-
@unmatched_symbol
|
34
|
-
end
|
35
|
-
|
36
|
-
def call
|
37
|
-
@run_once ||= begin
|
38
|
-
parse
|
39
|
-
true
|
40
|
-
end
|
41
|
-
self
|
42
|
-
end
|
43
|
-
|
44
|
-
def on_parse_error(msg)
|
45
|
-
return if @error_symbol && @unmatched_symbol
|
46
|
-
|
47
|
-
@error = msg
|
48
|
-
@unmatched_symbol = :unknown
|
49
|
-
|
50
|
-
case @error
|
51
|
-
when /unexpected end-of-input/
|
52
|
-
@error_symbol = :missing_end
|
53
|
-
when /expecting end-of-input/
|
54
|
-
@unmatched_symbol = :end
|
55
|
-
@error_symbol = :unmatched_syntax
|
56
|
-
when /unexpected .* expecting '(?<unmatched_symbol>.*)'/
|
57
|
-
@unmatched_symbol = $1.to_sym if $1
|
58
|
-
@error_symbol = :unmatched_syntax
|
59
|
-
when /unexpected `end'/, # Ruby 2.7 and 3.0
|
60
|
-
/unexpected end/, # Ruby 2.6
|
61
|
-
/unexpected keyword_end/i # Ruby 2.5
|
62
|
-
|
63
|
-
@error_symbol = :unmatched_syntax
|
64
|
-
else
|
65
|
-
@error_symbol = :unknown
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|