Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
## HEAD (unreleased)

- [Breaking] Remove previously deprecated `require "dead_end/fyi"` interface (https://github.com/zombocom/dead_end/pull/94)
- DeadEnd is now fired on EVERY syntax error (https://github.com/zombocom/dead_end/pull/94)
- Output format changes
- The "banner" is removed in favor of original parse error messages (https://github.com/zombocom/dead_end/pull/94)
- Parse errors emitted per-block rather than for the whole document (https://github.com/zombocom/dead_end/pull/94)

## 2.0.2

- Don't print terminal color codes when output is not tty (https://github.com/zombocom/dead_end/pull/91)
Expand Down
142 changes: 141 additions & 1 deletion lib/dead_end.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,144 @@
# frozen_string_literal: true

require_relative "dead_end/internals"
require_relative "dead_end/version"

require "tmpdir"
require "stringio"
require "pathname"
require "ripper"
require "timeout"

module DeadEnd
# Used to indicate a default value that cannot
# be confused with another input
DEFAULT_VALUE = Object.new.freeze

class Error < StandardError; end
TIMEOUT_DEFAULT = ENV.fetch("DEAD_END_TIMEOUT", 1).to_i

def self.handle_error(e)
filename = e.message.split(":").first
$stderr.sync = true

call(
source: Pathname(filename).read,
filename: filename
)

raise e
end

def self.call(source:, filename:, terminal: DEFAULT_VALUE, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
search = nil
Timeout.timeout(timeout) do
record_dir ||= ENV["DEBUG"] ? "tmp" : nil
search = CodeSearch.new(source, record_dir: record_dir).call
end

blocks = search.invalid_blocks
DisplayInvalidBlocks.new(
io: io,
blocks: blocks,
filename: filename,
terminal: terminal,
code_lines: search.code_lines
).call
rescue Timeout::Error => e
io.puts "Search timed out DEAD_END_TIMEOUT=#{timeout}, run with DEBUG=1 for more info"
io.puts e.backtrace.first(3).join($/)
end

# Used for counting spaces
module SpaceCount
def self.indent(string)
string.split(/\S/).first&.length || 0
end
end

# This will tell you if the `code_lines` would be valid
# if you removed the `without_lines`. In short it's a
# way to detect if we've found the lines with syntax errors
# in our document yet.
#
# code_lines = [
# CodeLine.new(line: "def foo\n", index: 0)
# CodeLine.new(line: " def bar\n", index: 1)
# CodeLine.new(line: "end\n", index: 2)
# ]
#
# DeadEnd.valid_without?(
# without_lines: code_lines[1],
# code_lines: code_lines
# ) # => true
#
# DeadEnd.valid?(code_lines) # => false
def self.valid_without?(without_lines:, code_lines:)
lines = code_lines - Array(without_lines).flatten

if lines.empty?
true
else
valid?(lines)
end
end

def self.invalid?(source)
source = source.join if source.is_a?(Array)
source = source.to_s

Ripper.new(source).tap(&:parse).error?
end

# Returns truthy if a given input source is valid syntax
#
# DeadEnd.valid?(<<~EOM) # => true
# def foo
# end
# EOM
#
# DeadEnd.valid?(<<~EOM) # => false
# def foo
# def bar # Syntax error here
# end
# EOM
#
# You can also pass in an array of lines and they'll be
# joined before evaluating
#
# DeadEnd.valid?(
# [
# "def foo\n",
# "end\n"
# ]
# ) # => true
#
# DeadEnd.valid?(
# [
# "def foo\n",
# " def bar\n", # Syntax error here
# "end\n"
# ]
# ) # => false
#
# As an FYI the CodeLine class instances respond to `to_s`
# so passing a CodeLine in as an object or as an array
# will convert it to it's code representation.
def self.valid?(source)
!invalid?(source)
end
end

require_relative "dead_end/code_line"
require_relative "dead_end/code_block"
require_relative "dead_end/code_search"
require_relative "dead_end/code_frontier"
require_relative "dead_end/clean_document"

require_relative "dead_end/lex_all"
require_relative "dead_end/block_expand"
require_relative "dead_end/around_block_scan"
require_relative "dead_end/ripper_errors"
require_relative "dead_end/display_invalid_blocks"
require_relative "dead_end/parse_blocks_from_indent_line"

require_relative "dead_end/auto"
2 changes: 1 addition & 1 deletion lib/dead_end/auto.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative "../dead_end/internals"
require_relative "../dead_end"

# Monkey patch kernel to ensure that all `require` calls call the same
# method
Expand Down
58 changes: 0 additions & 58 deletions lib/dead_end/banner.rb

This file was deleted.

11 changes: 6 additions & 5 deletions lib/dead_end/code_search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,13 @@ def record(block:, name: "record")
puts " block indent: #{block.current_indent}"
end
@record_dir.join(filename).open(mode: "a") do |f|
display = DisplayInvalidBlocks.new(
blocks: block,
document = DisplayCodeWithLineNumbers.new(
lines: @code_lines.select(&:visible?),
terminal: false,
code_lines: @code_lines
)
f.write(display.indent(display.code_with_lines))
highlight_lines: block.lines
).call

f.write(document)
end
end

Expand Down
73 changes: 28 additions & 45 deletions lib/dead_end/display_invalid_blocks.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# frozen_string_literal: true

require_relative "banner"
require_relative "capture_code_context"
require_relative "display_code_with_line_numbers"

Expand All @@ -9,18 +8,13 @@ module DeadEnd
class DisplayInvalidBlocks
attr_reader :filename

def initialize(code_lines:, blocks:, io: $stderr, filename: nil, terminal: DEFAULT_VALUE, invalid_obj: WhoDisSyntaxError::Null.new)
@terminal = terminal == DEFAULT_VALUE ? io.isatty : terminal

@filename = filename
def initialize(code_lines:, blocks:, io: $stderr, filename: nil, terminal: DEFAULT_VALUE)
@io = io

@blocks = Array(blocks)

@invalid_lines = @blocks.map(&:lines).flatten
@filename = filename
@code_lines = code_lines

@invalid_obj = invalid_obj
@terminal = terminal == DEFAULT_VALUE ? io.isatty : terminal
end

def document_ok?
Expand All @@ -30,46 +24,43 @@ def document_ok?
def call
if document_ok?
@io.puts "Syntax OK"
else
found_invalid_blocks
return self
end
self
end

private def no_invalid_blocks
@io.puts <<~EOM
EOM
end

private def found_invalid_blocks
@io.puts("--> #{filename}") if filename
@io.puts
if banner
@io.puts banner
@io.puts
@blocks.each do |block|
display_block(block)
end
@io.puts("file: #{filename}") if filename
@io.puts <<~EOM
simplified:

#{indent(code_block)}
EOM
self
end

def banner
Banner.new(invalid_obj: @invalid_obj).call
end
private def display_block(block)
lines = CaptureCodeContext.new(
blocks: block,
code_lines: @code_lines
).call

document = DisplayCodeWithLineNumbers.new(
lines: lines,
terminal: @terminal,
highlight_lines: block.lines
).call

RipperErrors.new(block.lines.map(&:original).join).call.errors.each do |e|
@io.puts e
end
@io.puts

def indent(string, with: " ")
string.each_line.map { |l| with + l }.join
@io.puts(document)
end

def code_block
string = +""
string << code_with_context
string
private def banner
Banner.new(invalid_obj: @invalid_obj).call
end

def code_with_context
private def code_with_context
lines = CaptureCodeContext.new(
blocks: @blocks,
code_lines: @code_lines
Expand All @@ -81,13 +72,5 @@ def code_with_context
highlight_lines: @invalid_lines
).call
end

def code_with_lines
DisplayCodeWithLineNumbers.new(
lines: @code_lines.select(&:visible?),
terminal: @terminal,
highlight_lines: @invalid_lines
).call
end
end
end
8 changes: 0 additions & 8 deletions lib/dead_end/fyi.rb

This file was deleted.

Loading