yard 0.9.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of yard might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -0
- data/Rakefile +1 -1
- data/docs/Tags.md +10 -9
- data/lib/rubygems_plugin.rb +7 -3
- data/lib/yard.rb +5 -6
- data/lib/yard/autoload.rb +12 -9
- data/lib/yard/cli/stats.rb +11 -1
- data/lib/yard/cli/yri.rb +2 -2
- data/lib/yard/code_objects/base.rb +12 -2
- data/lib/yard/code_objects/class_object.rb +2 -0
- data/lib/yard/code_objects/class_variable_object.rb +2 -0
- data/lib/yard/code_objects/constant_object.rb +2 -0
- data/lib/yard/code_objects/method_object.rb +3 -0
- data/lib/yard/code_objects/module_object.rb +2 -0
- data/lib/yard/code_objects/namespace_mapper.rb +113 -0
- data/lib/yard/code_objects/namespace_object.rb +3 -0
- data/lib/yard/docstring.rb +1 -1
- data/lib/yard/docstring_parser.rb +28 -1
- data/lib/yard/handlers/c/handler_methods.rb +1 -0
- data/lib/yard/handlers/c/mixin_handler.rb +7 -1
- data/lib/yard/handlers/ruby/alias_handler.rb +4 -3
- data/lib/yard/handlers/ruby/constant_handler.rb +6 -1
- data/lib/yard/handlers/ruby/dsl_handler_methods.rb +20 -3
- data/lib/yard/parser/c/comment_parser.rb +1 -1
- data/lib/yard/parser/ruby/ruby_parser.rb +25 -5
- data/lib/yard/parser/source_parser.rb +1 -0
- data/lib/yard/registry.rb +22 -43
- data/lib/yard/registry_resolver.rb +171 -0
- data/lib/yard/rubygems/hook.rb +164 -0
- data/lib/yard/server.rb +2 -1
- data/lib/yard/server/commands/root_request_command.rb +27 -0
- data/lib/yard/server/commands/static_file_command.rb +3 -16
- data/lib/yard/server/doc_server_helper.rb +1 -1
- data/lib/yard/server/router.rb +16 -6
- data/lib/yard/tags/default_factory.rb +3 -1
- data/lib/yard/tags/directives.rb +4 -0
- data/lib/yard/templates/engine.rb +1 -1
- data/lib/yard/templates/helpers/html_helper.rb +7 -2
- data/lib/yard/templates/helpers/module_helper.rb +1 -0
- data/lib/yard/templates/helpers/text_helper.rb +12 -0
- data/lib/yard/templates/template_options.rb +1 -1
- data/lib/yard/version.rb +1 -1
- data/spec/cli/stats_spec.rb +8 -3
- data/spec/code_objects/base_spec.rb +9 -1
- data/spec/docstring_parser_spec.rb +36 -1
- data/spec/docstring_spec.rb +1 -6
- data/spec/handlers/c/mixin_handler_spec.rb +16 -0
- data/spec/handlers/constant_handler_spec.rb +9 -0
- data/spec/handlers/dsl_handler_spec.rb +18 -0
- data/spec/handlers/examples/dsl_handler_001.rb.txt +29 -0
- data/spec/parser/ruby/ruby_parser_spec.rb +42 -0
- data/spec/registry_spec.rb +46 -3
- data/spec/server/router_spec.rb +1 -1
- data/spec/server_spec.rb +9 -0
- data/spec/tags/default_factory_spec.rb +5 -0
- data/spec/templates/engine_spec.rb +10 -0
- data/spec/templates/examples/constant001.txt +2 -2
- data/spec/templates/helpers/html_helper_spec.rb +8 -1
- data/spec/templates/helpers/module_helper_spec.rb +35 -0
- data/spec/templates/helpers/text_helper_spec.rb +20 -0
- data/templates/default/module/setup.rb +1 -1
- metadata +7 -4
- data/spec/server/commands/static_file_command_spec.rb +0 -84
@@ -117,7 +117,7 @@ module YARD
|
|
117
117
|
# Remove trailing/leading whitespace / newlines
|
118
118
|
@text = text.gsub(/\A[\r\n\s]+|[\r\n\s]+\Z/, '')
|
119
119
|
call_directives_after_parse
|
120
|
-
|
120
|
+
post_process
|
121
121
|
self
|
122
122
|
end
|
123
123
|
|
@@ -179,6 +179,17 @@ module YARD
|
|
179
179
|
docstring
|
180
180
|
end
|
181
181
|
|
182
|
+
# @!group Parser Callback Methods
|
183
|
+
|
184
|
+
# Call post processing callbacks on parser.
|
185
|
+
# This is called implicitly by parser. Use this when
|
186
|
+
# manually configuring a {Docstring} object.
|
187
|
+
#
|
188
|
+
# @return [void]
|
189
|
+
def post_process
|
190
|
+
call_after_parse_callbacks
|
191
|
+
end
|
192
|
+
|
182
193
|
# @!group Tag Manipulation Methods
|
183
194
|
|
184
195
|
# Creates a tag from the {Tags::DefaultFactory tag factory}.
|
@@ -296,6 +307,7 @@ module YARD
|
|
296
307
|
after_parse do |parser|
|
297
308
|
next unless parser.object
|
298
309
|
next unless parser.object.is_a?(CodeObjects::MethodObject)
|
310
|
+
next if parser.object.is_alias?
|
299
311
|
names = parser.object.parameters.map {|l| l.first.gsub(/\W/, '') }
|
300
312
|
seen_names = []
|
301
313
|
infile_info = "\n in file `#{parser.object.file}' " +
|
@@ -314,5 +326,20 @@ module YARD
|
|
314
326
|
end
|
315
327
|
end
|
316
328
|
end
|
329
|
+
|
330
|
+
# Define a callback to check that @see tags do not use {}.
|
331
|
+
after_parse do |parser|
|
332
|
+
next unless parser.object
|
333
|
+
|
334
|
+
parser.tags.each_with_index do |tag, i|
|
335
|
+
next unless tag.tag_name == "see"
|
336
|
+
if "#{tag.name}#{tag.text}" =~ /\A\{.*\}\Z/
|
337
|
+
infile_info = "\n in file `#{parser.object.file}' " +
|
338
|
+
"near line #{parser.object.line}"
|
339
|
+
log.warn "@see tag (##{i+1}) should not be wrapped in {} " +
|
340
|
+
"(causes rendering issues): #{infile_info}"
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
317
344
|
end
|
318
345
|
end
|
@@ -75,6 +75,7 @@ module YARD
|
|
75
75
|
{:read => name, :write => "#{name}="}.each do |type, meth_name|
|
76
76
|
next unless values[type] > 0
|
77
77
|
obj = handle_method(:instance, var_name, meth_name, nil)
|
78
|
+
register_file_info(obj, statement.file, statement.line)
|
78
79
|
obj.namespace.attributes[:instance][name] ||= SymbolHash[:read => nil, :write => nil]
|
79
80
|
obj.namespace.attributes[:instance][name][type] = obj
|
80
81
|
end
|
@@ -7,7 +7,13 @@ class YARD::Handlers::C::MixinHandler < YARD::Handlers::C::Base
|
|
7
7
|
statement.source.scan(MATCH) do |klass_var, mixin_var|
|
8
8
|
namespace = namespace_for_variable(klass_var)
|
9
9
|
ensure_loaded!(namespace)
|
10
|
-
|
10
|
+
|
11
|
+
if var = namespace_for_variable(mixin_var)
|
12
|
+
namespace.mixins(:instance) << var
|
13
|
+
else
|
14
|
+
raise YARD::Parser::UndocumentableError,
|
15
|
+
"CRuby mixin for unrecognized variable '#{mixin_var}'"
|
16
|
+
end
|
11
17
|
end
|
12
18
|
end
|
13
19
|
end
|
@@ -24,18 +24,19 @@ class YARD::Handlers::Ruby::AliasHandler < YARD::Handlers::Ruby::Base
|
|
24
24
|
new_obj = register MethodObject.new(namespace, new_meth, scope) do |o|
|
25
25
|
o.add_file(parser.file, statement.line)
|
26
26
|
end
|
27
|
+
namespace.aliases[new_obj] = old_meth
|
27
28
|
|
28
29
|
if old_obj
|
29
30
|
new_obj.signature = old_obj.signature
|
30
31
|
new_obj.source = old_obj.source
|
31
|
-
|
32
|
+
comments = [old_obj.docstring.to_raw, statement.comments].join("\n")
|
33
|
+
doc = YARD::Docstring.parser.parse(comments, new_obj, self)
|
34
|
+
new_obj.docstring = doc.to_docstring
|
32
35
|
new_obj.docstring.line_range = statement.comments_range
|
33
36
|
new_obj.docstring.hash_flag = statement.comments_hash_flag
|
34
37
|
new_obj.docstring.object = new_obj
|
35
38
|
else
|
36
39
|
new_obj.signature = "def #{new_meth}" # this is all we know.
|
37
40
|
end
|
38
|
-
|
39
|
-
namespace.aliases[new_obj] = old_meth
|
40
41
|
end
|
41
42
|
end
|
@@ -18,7 +18,12 @@ class YARD::Handlers::Ruby::ConstantHandler < YARD::Handlers::Ruby::Base
|
|
18
18
|
def process_constant(statement)
|
19
19
|
name = statement[0][0][0]
|
20
20
|
value = statement[1].source
|
21
|
-
|
21
|
+
obj = P(namespace, name)
|
22
|
+
if obj.is_a?(NamespaceObject)
|
23
|
+
raise YARD::Parser::UndocumentableError, "constant for existing #{obj.type} #{obj}"
|
24
|
+
else
|
25
|
+
register ConstantObject.new(namespace, name) {|o| o.source = statement; o.value = value.strip }
|
26
|
+
end
|
22
27
|
end
|
23
28
|
|
24
29
|
def process_structclass(statement)
|
@@ -15,14 +15,20 @@ module YARD
|
|
15
15
|
@docstring = statement.comments || ""
|
16
16
|
@docstring = @docstring.join("\n") if @docstring.is_a?(Array)
|
17
17
|
|
18
|
+
attaching = false
|
18
19
|
if @docstring =~ /^@!?macro\s+\[[^\]]*attach/
|
19
20
|
register_docstring(nil)
|
20
21
|
@docstring = ""
|
22
|
+
attaching = true
|
21
23
|
end
|
22
24
|
|
23
25
|
if macro = find_attached_macro
|
24
|
-
|
25
|
-
|
26
|
+
txt = macro.expand([caller_method, *call_params], statement.source)
|
27
|
+
@docstring += "\n" + txt
|
28
|
+
|
29
|
+
if !attaching && txt.match(/^\s*@!/) # macro has a directive
|
30
|
+
return register_docstring(nil)
|
31
|
+
end
|
26
32
|
elsif !statement.comments_hash_flag && !implicit_docstring?
|
27
33
|
return register_docstring(nil)
|
28
34
|
end
|
@@ -64,13 +70,24 @@ module YARD
|
|
64
70
|
def find_attached_macro
|
65
71
|
Registry.all(:macro).each do |macro|
|
66
72
|
next unless macro.method_object
|
67
|
-
next unless macro
|
73
|
+
next unless macro_name_matches(macro)
|
68
74
|
(namespace.inheritance_tree(true) + [P('Object')]).each do |obj|
|
69
75
|
return macro if obj == macro.method_object.namespace
|
70
76
|
end
|
71
77
|
end
|
72
78
|
nil
|
73
79
|
end
|
80
|
+
|
81
|
+
# @return [Boolean] whether caller method matches a macro or
|
82
|
+
# its alias names.
|
83
|
+
def macro_name_matches(macro)
|
84
|
+
objs = [macro.method_object]
|
85
|
+
if objs.first.type != :proxy && objs.first.respond_to?(:aliases)
|
86
|
+
objs.push(*objs.first.aliases)
|
87
|
+
end
|
88
|
+
|
89
|
+
objs.any? {|obj| obj.name.to_s == caller_method.to_s }
|
90
|
+
end
|
74
91
|
end
|
75
92
|
end
|
76
93
|
end
|
@@ -28,7 +28,7 @@ module YARD
|
|
28
28
|
|
29
29
|
def parse_overrides(comments)
|
30
30
|
comments.map do |line|
|
31
|
-
type, name = *line.scan(/^\s*Document-(class|module|method|const):\s*(\S.*)\s*$/).first
|
31
|
+
type, name = *line.scan(/^\s*Document-(class|module|method|attr|const):\s*(\S.*)\s*$/).first
|
32
32
|
if type
|
33
33
|
@overrides << [type.to_sym, name]
|
34
34
|
nil
|
@@ -5,6 +5,7 @@ module YARD
|
|
5
5
|
module Ruby
|
6
6
|
# Ruby 1.9 parser
|
7
7
|
# @!attribute [r] encoding_line
|
8
|
+
# @!attribute [r] frozen_string_line
|
8
9
|
# @!attribute [r] shebang_line
|
9
10
|
# @!attribute [r] enumerator
|
10
11
|
class RubyParser < Parser::Base
|
@@ -17,13 +18,14 @@ module YARD
|
|
17
18
|
def enumerator; @parser.enumerator end
|
18
19
|
def shebang_line; @parser.shebang_line end
|
19
20
|
def encoding_line; @parser.encoding_line end
|
21
|
+
def frozen_string_line; @parser.frozen_string_line end
|
20
22
|
end
|
21
23
|
|
22
24
|
# Internal parser class
|
23
25
|
# @since 0.5.6
|
24
26
|
class RipperParser < Ripper
|
25
27
|
attr_reader :ast, :charno, :comments, :file, :tokens
|
26
|
-
attr_reader :shebang_line, :encoding_line
|
28
|
+
attr_reader :shebang_line, :encoding_line, :frozen_string_line
|
27
29
|
alias root ast
|
28
30
|
|
29
31
|
def initialize(source, filename, *args)
|
@@ -43,6 +45,7 @@ module YARD
|
|
43
45
|
@charno = 0
|
44
46
|
@shebang_line = nil
|
45
47
|
@encoding_line = nil
|
48
|
+
@frozen_string_line = nil
|
46
49
|
@file_encoding = nil
|
47
50
|
end
|
48
51
|
|
@@ -194,7 +197,8 @@ module YARD
|
|
194
197
|
begin; undef on_#{event}; rescue NameError; end
|
195
198
|
def on_#{event}(tok)
|
196
199
|
unless @last_ns_token == [:kw, "def"] ||
|
197
|
-
(@tokens.last && @tokens.last[0] == :symbeg)
|
200
|
+
(@tokens.last && @tokens.last[0] == :symbeg) ||
|
201
|
+
(!@newline && %w(if while until unless).include?(tok))
|
198
202
|
(@map[tok] ||= []) << [lineno, charno]
|
199
203
|
end
|
200
204
|
visit_ns_token(:#{event}, tok, true)
|
@@ -202,16 +206,22 @@ module YARD
|
|
202
206
|
eof
|
203
207
|
end
|
204
208
|
|
205
|
-
[:
|
209
|
+
[:nl, :ignored_nl].each do |event|
|
206
210
|
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
207
211
|
begin; undef on_#{event}; rescue NameError; end
|
208
212
|
def on_#{event}(tok)
|
209
213
|
add_token(:#{event}, tok)
|
210
|
-
@
|
214
|
+
@newline = true
|
215
|
+
@charno += tok ? tok.length : 1
|
211
216
|
end
|
212
217
|
eof
|
213
218
|
end
|
214
219
|
|
220
|
+
def on_sp(tok)
|
221
|
+
add_token(:sp, tok)
|
222
|
+
@charno += tok.length
|
223
|
+
end
|
224
|
+
|
215
225
|
def visit_event(node)
|
216
226
|
map = @map[MAPPINGS[node.type]]
|
217
227
|
lstart, sstart = *(map ? map.pop : [lineno, @ns_charno - 1])
|
@@ -239,6 +249,7 @@ module YARD
|
|
239
249
|
@last_ns_token = [token, data]
|
240
250
|
@charno += data.length
|
241
251
|
@ns_charno = charno
|
252
|
+
@newline = [:semicolon, :comment, :kw, :op].include?(token)
|
242
253
|
if ast_token
|
243
254
|
AstNode.new(token, [data], :line => lineno..lineno, :char => ch..charno-1, :token => true)
|
244
255
|
end
|
@@ -390,7 +401,7 @@ module YARD
|
|
390
401
|
ReferenceNode.new(:const_path_ref, args, :listline => lineno..lineno, :listchar => charno..charno)
|
391
402
|
end
|
392
403
|
|
393
|
-
[:if_mod, :unless_mod, :while_mod].each do |kw|
|
404
|
+
[:if_mod, :unless_mod, :while_mod, :until_mod].each do |kw|
|
394
405
|
node_class = AstNode.node_class_for(kw)
|
395
406
|
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
396
407
|
begin; undef on_#{kw}; rescue NameError; end
|
@@ -489,6 +500,9 @@ module YARD
|
|
489
500
|
elsif comment =~ SourceParser::ENCODING_LINE
|
490
501
|
@encoding_line = comment
|
491
502
|
not_comment = true
|
503
|
+
elsif comment =~ SourceParser::FROZEN_STRING_LINE
|
504
|
+
@frozen_string_line = comment
|
505
|
+
not_comment = true
|
492
506
|
end
|
493
507
|
end
|
494
508
|
|
@@ -557,6 +571,12 @@ module YARD
|
|
557
571
|
def insert_comments
|
558
572
|
root.traverse do |node|
|
559
573
|
next if node.type == :comment || node.type == :list || node.parent.type != :list
|
574
|
+
|
575
|
+
# never attach comments to if/unless mod nodes
|
576
|
+
if node.type == :if_mod || node.type == :unless_mod
|
577
|
+
node = node.then_block
|
578
|
+
end
|
579
|
+
|
560
580
|
# check upwards from line before node; check node's line at the end
|
561
581
|
((node.line-1).downto(node.line-2).to_a + [node.line]).each do |line|
|
562
582
|
comment = @comments[line]
|
@@ -62,6 +62,7 @@ module YARD
|
|
62
62
|
class SourceParser
|
63
63
|
SHEBANG_LINE = /\A\s*#!\S+/
|
64
64
|
ENCODING_LINE = /\A(?:\s*#*!.*\r?\n)?\s*(?:#+|\/\*+|\/\/+).*coding\s*[:=]{1,2}\s*([a-z\d_\-]+)/i
|
65
|
+
FROZEN_STRING_LINE = /frozen(-|_)string(-|_)literal: true/i
|
65
66
|
|
66
67
|
# The default glob of files to be parsed.
|
67
68
|
# @since 0.9.0
|
data/lib/yard/registry.rb
CHANGED
@@ -30,7 +30,7 @@ module YARD
|
|
30
30
|
# Registry.resolve(P('YARD::CodeObjects::Base'), '#docstring', true)
|
31
31
|
module Registry
|
32
32
|
DEFAULT_YARDOC_FILE = ".yardoc"
|
33
|
-
LOCAL_YARDOC_INDEX = File.expand_path('
|
33
|
+
LOCAL_YARDOC_INDEX = File.expand_path(File.join(Config::CONFIG_DIR, 'gem_index'))
|
34
34
|
DEFAULT_PO_DIR = "po"
|
35
35
|
|
36
36
|
extend Enumerable
|
@@ -61,10 +61,12 @@ module YARD
|
|
61
61
|
|
62
62
|
if for_writing
|
63
63
|
global_yardoc_file(spec, for_writing) ||
|
64
|
+
old_global_yardoc_file(spec, for_writing) ||
|
64
65
|
local_yardoc_file(spec, for_writing)
|
65
66
|
else
|
66
67
|
local_yardoc_file(spec, for_writing) ||
|
67
|
-
global_yardoc_file(spec, for_writing)
|
68
|
+
global_yardoc_file(spec, for_writing) ||
|
69
|
+
old_global_yardoc_file(spec, for_writing)
|
68
70
|
end
|
69
71
|
end
|
70
72
|
|
@@ -276,47 +278,9 @@ module YARD
|
|
276
278
|
# @return [nil] if +proxy_fallback+ is +false+ and no object was found.
|
277
279
|
# @see P
|
278
280
|
def resolve(namespace, name, inheritance = false, proxy_fallback = false, type = nil)
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
if namespace == :root || !namespace
|
284
|
-
namespace = root
|
285
|
-
else
|
286
|
-
namespace = namespace.parent until namespace.is_a?(CodeObjects::NamespaceObject)
|
287
|
-
end
|
288
|
-
orignamespace = namespace
|
289
|
-
|
290
|
-
name = name.to_s
|
291
|
-
if name =~ /^#{CodeObjects::NSEPQ}/
|
292
|
-
[name, name[2..-1]].each do |n|
|
293
|
-
found = at(n)
|
294
|
-
return found if found && (type.nil? || found.type == type)
|
295
|
-
end
|
296
|
-
else
|
297
|
-
while namespace
|
298
|
-
if namespace.is_a?(CodeObjects::NamespaceObject)
|
299
|
-
if inheritance
|
300
|
-
nss = namespace.inheritance_tree(true)
|
301
|
-
if namespace.respond_to?(:superclass)
|
302
|
-
if namespace.superclass != P('BasicObject')
|
303
|
-
nss |= [P('Object')]
|
304
|
-
end
|
305
|
-
nss |= [P('BasicObject')]
|
306
|
-
end
|
307
|
-
else
|
308
|
-
nss = [namespace]
|
309
|
-
end
|
310
|
-
nss.each do |ns|
|
311
|
-
next if ns.is_a?(CodeObjects::Proxy)
|
312
|
-
found = partial_resolve(ns, name, type)
|
313
|
-
return found if found
|
314
|
-
end
|
315
|
-
end
|
316
|
-
namespace = namespace.parent
|
317
|
-
end
|
318
|
-
end
|
319
|
-
proxy_fallback ? CodeObjects::Proxy.new(orignamespace, name, type) : nil
|
281
|
+
thread_local_resolver.lookup_by_path name,
|
282
|
+
:namespace => namespace, :inheritance => inheritance,
|
283
|
+
:proxy_fallback => proxy_fallback, :type => type
|
320
284
|
end
|
321
285
|
|
322
286
|
# @group Managing Source File Checksums
|
@@ -402,6 +366,16 @@ module YARD
|
|
402
366
|
# @group Retrieving yardoc File Locations
|
403
367
|
|
404
368
|
def global_yardoc_file(spec, for_writing = false)
|
369
|
+
path = spec.doc_dir
|
370
|
+
yfile = spec.doc_dir(DEFAULT_YARDOC_FILE)
|
371
|
+
if for_writing && File.writable?(path)
|
372
|
+
return yfile
|
373
|
+
elsif !for_writing && File.exist?(yfile)
|
374
|
+
return yfile
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def old_global_yardoc_file(spec, for_writing = false)
|
405
379
|
path = spec.full_gem_path
|
406
380
|
yfile = File.join(path, DEFAULT_YARDOC_FILE)
|
407
381
|
if for_writing && File.writable?(path)
|
@@ -433,6 +407,11 @@ module YARD
|
|
433
407
|
def thread_local_store=(value)
|
434
408
|
Thread.current[:__yard_registry__] = value
|
435
409
|
end
|
410
|
+
|
411
|
+
# @since 0.9.1
|
412
|
+
def thread_local_resolver
|
413
|
+
Thread.current[:__yard_resolver__] ||= RegistryResolver.new
|
414
|
+
end
|
436
415
|
end
|
437
416
|
end
|
438
417
|
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
module YARD
|
2
|
+
# Handles all logic for complex lexical and inherited object resolution.
|
3
|
+
# Used by {Registry.resolve}, so there is no need to use this class
|
4
|
+
# directly.
|
5
|
+
#
|
6
|
+
# @see Registry.resolve
|
7
|
+
class RegistryResolver
|
8
|
+
include CodeObjects::NamespaceMapper
|
9
|
+
|
10
|
+
# Creates a new resolver object for a registry.
|
11
|
+
#
|
12
|
+
# @param registry [Registry] only set this if customizing the registry
|
13
|
+
# object
|
14
|
+
def initialize(registry = Registry)
|
15
|
+
@registry = Registry
|
16
|
+
end
|
17
|
+
|
18
|
+
# Performs a lookup on a given path in the registry. Resolution will occur
|
19
|
+
# in a similar way to standard Ruby identifier resolution, doing lexical
|
20
|
+
# lookup, as well as (optionally) through the inheritance chain. A proxy
|
21
|
+
# object can be returned if the lookup fails for future resolution. The
|
22
|
+
# proxy will be type hinted with the +type+ used in the original lookup.
|
23
|
+
#
|
24
|
+
# @option opts namespace [CodeObjects::Base, :root, nil] (nil) the namespace
|
25
|
+
# object to start searching from. If root or nil is provided, {Registry.root}
|
26
|
+
# is assumed.
|
27
|
+
# @option opts inheritance [Boolean] (false) whether to perform lookups through
|
28
|
+
# the inheritance chain (includes mixins)
|
29
|
+
# @option opts proxy_fallback [Boolean] (false) when true, a proxy is returned
|
30
|
+
# if no match is found
|
31
|
+
# @option opts type [Symbol] (nil) an optional type hint for the resolver
|
32
|
+
# to consider when performing a lookup. If a type is provided and the
|
33
|
+
# resolved object's type does not match the hint, the object is discarded.
|
34
|
+
# @return [CodeObjects::Base, CodeObjects::Proxy, nil] the first object
|
35
|
+
# that matches the path lookup. If proxy_fallback is provided, a proxy
|
36
|
+
# object will be returned in the event of no match, otherwise nil will
|
37
|
+
# be returned.
|
38
|
+
# @example A lookup from root
|
39
|
+
# resolver.lookup_by_path("A::B::C")
|
40
|
+
# @example A lookup from the A::B namespace
|
41
|
+
# resolver.lookup_by_path("C", namespace: P("A::B"))
|
42
|
+
# @example A lookup on a method through the inheritance tree
|
43
|
+
# resolver.lookup_by_math("A::B#foo", inheritance: true)
|
44
|
+
def lookup_by_path(path, opts = {})
|
45
|
+
path = path.to_s
|
46
|
+
namespace = opts[:namespace]
|
47
|
+
inheritance = opts[:inheritance] || false
|
48
|
+
proxy_fallback = opts[:proxy_fallback] || false
|
49
|
+
type = opts[:type]
|
50
|
+
|
51
|
+
if namespace.is_a?(CodeObjects::Proxy)
|
52
|
+
return proxy_fallback ? CodeObjects::Proxy.new(namespace, path, type) : nil
|
53
|
+
end
|
54
|
+
|
55
|
+
if namespace == :root || !namespace
|
56
|
+
namespace = @registry.root
|
57
|
+
else
|
58
|
+
namespace = namespace.parent until namespace.is_a?(CodeObjects::NamespaceObject)
|
59
|
+
end
|
60
|
+
orignamespace = namespace
|
61
|
+
|
62
|
+
if path =~ /\A#{default_separator}/
|
63
|
+
path, namespace = $', @registry.root
|
64
|
+
end
|
65
|
+
|
66
|
+
resolved = nil
|
67
|
+
while namespace && !resolved
|
68
|
+
resolved = lookup_path_direct(namespace, path, type)
|
69
|
+
resolved ||= lookup_path_inherited(namespace, path, type) if inheritance
|
70
|
+
namespace = namespace.parent
|
71
|
+
end
|
72
|
+
|
73
|
+
if proxy_fallback
|
74
|
+
resolved ||= CodeObjects::Proxy.new(orignamespace, path, type)
|
75
|
+
end
|
76
|
+
|
77
|
+
resolved
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# return [Boolean] if the obj's type matches the provided type.
|
83
|
+
def validate(obj, type)
|
84
|
+
return !type || (obj && obj.type == type) ? obj : nil
|
85
|
+
end
|
86
|
+
|
87
|
+
# Performs a lexical lookup from a namespace for a path and a type hint.
|
88
|
+
def lookup_path_direct(namespace, path, type)
|
89
|
+
if namespace.root? && result = validate(@registry.at(path), type)
|
90
|
+
return result
|
91
|
+
end
|
92
|
+
|
93
|
+
if path =~ /\A(#{separators_match})/
|
94
|
+
return validate(@registry.at(namespace.path + path), type)
|
95
|
+
end
|
96
|
+
|
97
|
+
separators.each do |sep|
|
98
|
+
result = validate(@registry.at(namespace.path + sep + path), type)
|
99
|
+
return result if result
|
100
|
+
end
|
101
|
+
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
# Performs a lookup through the inheritance chain on a path with a type hint.
|
106
|
+
def lookup_path_inherited(namespace, path, type)
|
107
|
+
resolved, last_obj, scopes, last_sep, pos = nil, namespace, [], nil, 0
|
108
|
+
|
109
|
+
if path =~ /\A(#{separators_match})/
|
110
|
+
last_sep, path = $1, $'
|
111
|
+
end
|
112
|
+
|
113
|
+
path.scan(/(.+?)(#{separators_match}|$)/).each do |part, sep|
|
114
|
+
cur_obj = nil
|
115
|
+
pos += "#{part}#{sep}".length
|
116
|
+
parsed_end = pos == path.length
|
117
|
+
|
118
|
+
if !last_obj || (!parsed_end && !last_obj.is_a?(CodeObjects::NamespaceObject))
|
119
|
+
break # can't continue
|
120
|
+
end
|
121
|
+
|
122
|
+
collect_namespaces(last_obj).each do |ns|
|
123
|
+
next if ns.is_a?(CodeObjects::Proxy)
|
124
|
+
|
125
|
+
found, search_seps = nil, []
|
126
|
+
scopes.each do |scope|
|
127
|
+
search_seps += separators_for_type(scope)
|
128
|
+
end
|
129
|
+
|
130
|
+
if search_seps.empty?
|
131
|
+
if ns.type == :root
|
132
|
+
search_seps = [""]
|
133
|
+
elsif last_sep.nil?
|
134
|
+
search_seps = separators
|
135
|
+
else
|
136
|
+
search_seps = [@default_sep]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
([last_sep] | search_seps).compact.each do |search_sep|
|
141
|
+
break if found = @registry.at(ns.path + search_sep.to_s + part)
|
142
|
+
end
|
143
|
+
|
144
|
+
break cur_obj = found if found
|
145
|
+
end
|
146
|
+
|
147
|
+
last_sep = sep
|
148
|
+
scopes = types_for_separator(sep) || []
|
149
|
+
last_obj = cur_obj
|
150
|
+
resolved = cur_obj if parsed_end && cur_obj && (type.nil? || type == cur_obj.type)
|
151
|
+
end
|
152
|
+
|
153
|
+
resolved
|
154
|
+
end
|
155
|
+
|
156
|
+
# Collects and returns all inherited namespaces for a given object
|
157
|
+
def collect_namespaces(object)
|
158
|
+
return [] if !object
|
159
|
+
|
160
|
+
nss = object.inheritance_tree(true)
|
161
|
+
if object.respond_to?(:superclass)
|
162
|
+
if object.superclass != P('BasicObject')
|
163
|
+
nss |= [P('Object')]
|
164
|
+
end
|
165
|
+
nss |= [P('BasicObject')]
|
166
|
+
end
|
167
|
+
|
168
|
+
nss
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|