lib/dead_end.rb in dead_end-2.0.2 vs lib/dead_end.rb in dead_end-3.0.0
- old
+ new
@@ -1,4 +1,148 @@
# 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: DEFAULT_VALUE, terminal: DEFAULT_VALUE, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
+ search = nil
+ filename = nil if filename == DEFAULT_VALUE
+ 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/explain_syntax"
+
require_relative "dead_end/auto"
+require_relative "dead_end/cli"