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.

Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/Rakefile +1 -1
  4. data/docs/Tags.md +10 -9
  5. data/lib/rubygems_plugin.rb +7 -3
  6. data/lib/yard.rb +5 -6
  7. data/lib/yard/autoload.rb +12 -9
  8. data/lib/yard/cli/stats.rb +11 -1
  9. data/lib/yard/cli/yri.rb +2 -2
  10. data/lib/yard/code_objects/base.rb +12 -2
  11. data/lib/yard/code_objects/class_object.rb +2 -0
  12. data/lib/yard/code_objects/class_variable_object.rb +2 -0
  13. data/lib/yard/code_objects/constant_object.rb +2 -0
  14. data/lib/yard/code_objects/method_object.rb +3 -0
  15. data/lib/yard/code_objects/module_object.rb +2 -0
  16. data/lib/yard/code_objects/namespace_mapper.rb +113 -0
  17. data/lib/yard/code_objects/namespace_object.rb +3 -0
  18. data/lib/yard/docstring.rb +1 -1
  19. data/lib/yard/docstring_parser.rb +28 -1
  20. data/lib/yard/handlers/c/handler_methods.rb +1 -0
  21. data/lib/yard/handlers/c/mixin_handler.rb +7 -1
  22. data/lib/yard/handlers/ruby/alias_handler.rb +4 -3
  23. data/lib/yard/handlers/ruby/constant_handler.rb +6 -1
  24. data/lib/yard/handlers/ruby/dsl_handler_methods.rb +20 -3
  25. data/lib/yard/parser/c/comment_parser.rb +1 -1
  26. data/lib/yard/parser/ruby/ruby_parser.rb +25 -5
  27. data/lib/yard/parser/source_parser.rb +1 -0
  28. data/lib/yard/registry.rb +22 -43
  29. data/lib/yard/registry_resolver.rb +171 -0
  30. data/lib/yard/rubygems/hook.rb +164 -0
  31. data/lib/yard/server.rb +2 -1
  32. data/lib/yard/server/commands/root_request_command.rb +27 -0
  33. data/lib/yard/server/commands/static_file_command.rb +3 -16
  34. data/lib/yard/server/doc_server_helper.rb +1 -1
  35. data/lib/yard/server/router.rb +16 -6
  36. data/lib/yard/tags/default_factory.rb +3 -1
  37. data/lib/yard/tags/directives.rb +4 -0
  38. data/lib/yard/templates/engine.rb +1 -1
  39. data/lib/yard/templates/helpers/html_helper.rb +7 -2
  40. data/lib/yard/templates/helpers/module_helper.rb +1 -0
  41. data/lib/yard/templates/helpers/text_helper.rb +12 -0
  42. data/lib/yard/templates/template_options.rb +1 -1
  43. data/lib/yard/version.rb +1 -1
  44. data/spec/cli/stats_spec.rb +8 -3
  45. data/spec/code_objects/base_spec.rb +9 -1
  46. data/spec/docstring_parser_spec.rb +36 -1
  47. data/spec/docstring_spec.rb +1 -6
  48. data/spec/handlers/c/mixin_handler_spec.rb +16 -0
  49. data/spec/handlers/constant_handler_spec.rb +9 -0
  50. data/spec/handlers/dsl_handler_spec.rb +18 -0
  51. data/spec/handlers/examples/dsl_handler_001.rb.txt +29 -0
  52. data/spec/parser/ruby/ruby_parser_spec.rb +42 -0
  53. data/spec/registry_spec.rb +46 -3
  54. data/spec/server/router_spec.rb +1 -1
  55. data/spec/server_spec.rb +9 -0
  56. data/spec/tags/default_factory_spec.rb +5 -0
  57. data/spec/templates/engine_spec.rb +10 -0
  58. data/spec/templates/examples/constant001.txt +2 -2
  59. data/spec/templates/helpers/html_helper_spec.rb +8 -1
  60. data/spec/templates/helpers/module_helper_spec.rb +35 -0
  61. data/spec/templates/helpers/text_helper_spec.rb +20 -0
  62. data/templates/default/module/setup.rb +1 -1
  63. metadata +7 -4
  64. 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
- call_after_parse_callbacks
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
- namespace.mixins(:instance) << namespace_for_variable(mixin_var)
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
- new_obj.docstring = old_obj.docstring + YARD::Docstring.new(statement.comments)
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
- register ConstantObject.new(namespace, name) {|o| o.source = statement; o.value = value.strip }
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
- @docstring += "\n" +
25
- macro.expand([caller_method, *call_params], statement.source)
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.method_object.name.to_s == caller_method.to_s
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
- [:sp, :nl, :ignored_nl].each do |event|
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
- @charno += tok.length
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('~/.yard/gem_index')
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
- if namespace.is_a?(CodeObjects::Proxy)
280
- return proxy_fallback ? CodeObjects::Proxy.new(namespace, name, type) : nil
281
- end
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