-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsource_finder.rb
139 lines (121 loc) · 4.25 KB
/
source_finder.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# frozen_string_literal: true
require_relative "ruby-lex"
module IRB
class SourceFinder
class EvaluationError < StandardError; end
class Source
attr_reader :file, :line
def initialize(file, line, ast_source = nil)
@file = file
@line = line
@ast_source = ast_source
end
def file_exist?
File.exist?(@file)
end
def binary_file?
# If the line is zero, it means that the target's source is probably in a binary file.
@line.zero?
end
def file_content
@file_content ||= File.read(@file)
end
def colorized_content
if !binary_file? && file_exist?
end_line = find_end
# To correctly colorize, we need to colorize full content and extract the relevant lines.
colored = IRB::Color.colorize_code(file_content)
colored.lines[@line - 1...end_line].join
elsif @ast_source
IRB::Color.colorize_code(@ast_source)
end
end
private
def find_end
lex = RubyLex.new
code = file_content
lines = code.lines[(@line - 1)..-1]
tokens = RubyLex.ripper_lex_without_warning(lines.join)
prev_tokens = []
# chunk with line number
tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk|
code = lines[0..lnum].join
prev_tokens.concat chunk
continue = lex.should_continue?(prev_tokens)
syntax = lex.check_code_syntax(code, local_variables: [])
if !continue && syntax == :valid
return @line + lnum
end
end
@line
end
end
private_constant :Source
def initialize(irb_context)
@irb_context = irb_context
end
def find_source(signature, super_level = 0)
case signature
when /\A(::)?[A-Z]\w*(::[A-Z]\w*)*\z/ # ConstName, ::ConstName, ConstPath::ConstName
eval_receiver_or_owner(signature) # trigger autoload
*parts, name = signature.split('::', -1)
base =
if parts.empty? # ConstName
find_const_owner(name)
elsif parts == [''] # ::ConstName
Object
else # ConstPath::ConstName
eval_receiver_or_owner(parts.join('::'))
end
file, line = base.const_source_location(name)
when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method
owner = eval_receiver_or_owner(Regexp.last_match[:owner])
method = Regexp.last_match[:method]
return unless owner.respond_to?(:instance_method)
method = method_target(owner, super_level, method, "owner")
file, line = method&.source_location
when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method
receiver = eval_receiver_or_owner(Regexp.last_match[:receiver] || 'self')
method = Regexp.last_match[:method]
return unless receiver.respond_to?(method, true)
method = method_target(receiver, super_level, method, "receiver")
file, line = method&.source_location
end
return unless file && line
if File.exist?(file)
Source.new(file, line)
elsif method
# Method defined with eval, probably in IRB session
source = RubyVM::AbstractSyntaxTree.of(method)&.source rescue nil
Source.new(file, line, source)
end
rescue EvaluationError
nil
end
private
def method_target(owner_receiver, super_level, method, type)
case type
when "owner"
target_method = owner_receiver.instance_method(method)
when "receiver"
target_method = owner_receiver.method(method)
end
super_level.times do |s|
target_method = target_method.super_method if target_method
end
target_method
rescue NameError
nil
end
def eval_receiver_or_owner(code)
context_binding = @irb_context.workspace.binding
eval(code, context_binding)
rescue NameError
raise EvaluationError
end
def find_const_owner(name)
module_nesting = @irb_context.workspace.binding.eval('::Module.nesting')
module_nesting.find { |mod| mod.const_defined?(name, false) } || module_nesting.find { |mod| mod.const_defined?(name) } || Object
end
end
end