yoda-language-server 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +8 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +78 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +85 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/client/atom/main.js +27 -0
  13. data/client/vscode/.gitignore +4 -0
  14. data/client/vscode/.vscode/launch.json +28 -0
  15. data/client/vscode/.vscode/settings.json +9 -0
  16. data/client/vscode/.vscode/tasks.json +20 -0
  17. data/client/vscode/.vscodeignore +8 -0
  18. data/client/vscode/CHANGELOG.md +7 -0
  19. data/client/vscode/README.md +65 -0
  20. data/client/vscode/package-lock.json +2688 -0
  21. data/client/vscode/package.json +39 -0
  22. data/client/vscode/src/extension.ts +42 -0
  23. data/client/vscode/src/test/extension.test.ts +22 -0
  24. data/client/vscode/src/test/index.ts +22 -0
  25. data/client/vscode/tsconfig.json +16 -0
  26. data/client/vscode/vsc-extension-quickstart.md +33 -0
  27. data/exe/yoda +27 -0
  28. data/lib/yoda.rb +11 -0
  29. data/lib/yoda/evaluation.rb +9 -0
  30. data/lib/yoda/evaluation/code_completion.rb +65 -0
  31. data/lib/yoda/evaluation/code_completion/base_provider.rb +57 -0
  32. data/lib/yoda/evaluation/code_completion/const_provider.rb +90 -0
  33. data/lib/yoda/evaluation/code_completion/method_provider.rb +82 -0
  34. data/lib/yoda/evaluation/code_completion/variable_provider.rb +18 -0
  35. data/lib/yoda/evaluation/comment_completion.rb +70 -0
  36. data/lib/yoda/evaluation/comment_completion/base_provider.rb +64 -0
  37. data/lib/yoda/evaluation/comment_completion/param_provider.rb +18 -0
  38. data/lib/yoda/evaluation/comment_completion/tag_provider.rb +41 -0
  39. data/lib/yoda/evaluation/comment_completion/type_provider.rb +58 -0
  40. data/lib/yoda/evaluation/current_node_explain.rb +70 -0
  41. data/lib/yoda/evaluation/evaluator.rb +103 -0
  42. data/lib/yoda/evaluation/signature_discovery.rb +83 -0
  43. data/lib/yoda/model.rb +12 -0
  44. data/lib/yoda/model/completion_item.rb +56 -0
  45. data/lib/yoda/model/descriptions.rb +10 -0
  46. data/lib/yoda/model/descriptions/base.rb +26 -0
  47. data/lib/yoda/model/descriptions/function_description.rb +40 -0
  48. data/lib/yoda/model/descriptions/value_description.rb +33 -0
  49. data/lib/yoda/model/descriptions/word_description.rb +32 -0
  50. data/lib/yoda/model/function_signatures.rb +13 -0
  51. data/lib/yoda/model/function_signatures/base.rb +68 -0
  52. data/lib/yoda/model/function_signatures/constructor.rb +70 -0
  53. data/lib/yoda/model/function_signatures/formatter.rb +82 -0
  54. data/lib/yoda/model/function_signatures/method.rb +67 -0
  55. data/lib/yoda/model/function_signatures/overload.rb +79 -0
  56. data/lib/yoda/model/function_signatures/parameter_list.rb +108 -0
  57. data/lib/yoda/model/function_signatures/type_builder.rb +101 -0
  58. data/lib/yoda/model/node_signature.rb +28 -0
  59. data/lib/yoda/model/path.rb +96 -0
  60. data/lib/yoda/model/scoped_path.rb +44 -0
  61. data/lib/yoda/model/types.rb +84 -0
  62. data/lib/yoda/model/types/any_type.rb +32 -0
  63. data/lib/yoda/model/types/base.rb +37 -0
  64. data/lib/yoda/model/types/duck_type.rb +41 -0
  65. data/lib/yoda/model/types/function_type.rb +174 -0
  66. data/lib/yoda/model/types/generic_type.rb +66 -0
  67. data/lib/yoda/model/types/instance_type.rb +42 -0
  68. data/lib/yoda/model/types/module_type.rb +42 -0
  69. data/lib/yoda/model/types/sequence_type.rb +53 -0
  70. data/lib/yoda/model/types/union_type.rb +56 -0
  71. data/lib/yoda/model/types/unknown_type.rb +40 -0
  72. data/lib/yoda/model/types/value_type.rb +58 -0
  73. data/lib/yoda/model/values.rb +9 -0
  74. data/lib/yoda/model/values/base.rb +32 -0
  75. data/lib/yoda/model/values/instance_value.rb +65 -0
  76. data/lib/yoda/model/values/module_value.rb +72 -0
  77. data/lib/yoda/parsing.rb +15 -0
  78. data/lib/yoda/parsing/ast_traversable.rb +18 -0
  79. data/lib/yoda/parsing/comment_tokenizer.rb +59 -0
  80. data/lib/yoda/parsing/location.rb +101 -0
  81. data/lib/yoda/parsing/node_objects.rb +10 -0
  82. data/lib/yoda/parsing/node_objects/const_node.rb +52 -0
  83. data/lib/yoda/parsing/node_objects/method_definition.rb +46 -0
  84. data/lib/yoda/parsing/node_objects/namespace.rb +104 -0
  85. data/lib/yoda/parsing/node_objects/send_node.rb +72 -0
  86. data/lib/yoda/parsing/parser.rb +27 -0
  87. data/lib/yoda/parsing/query.rb +11 -0
  88. data/lib/yoda/parsing/query/current_comment_query.rb +80 -0
  89. data/lib/yoda/parsing/query/current_comment_token_query.rb +153 -0
  90. data/lib/yoda/parsing/query/current_commenting_node_query.rb +68 -0
  91. data/lib/yoda/parsing/query/current_location_node_query.rb +51 -0
  92. data/lib/yoda/parsing/query/current_node_comment_query.rb +40 -0
  93. data/lib/yoda/parsing/range.rb +41 -0
  94. data/lib/yoda/parsing/scopes.rb +15 -0
  95. data/lib/yoda/parsing/scopes/base.rb +78 -0
  96. data/lib/yoda/parsing/scopes/builder.rb +60 -0
  97. data/lib/yoda/parsing/scopes/class_definition.rb +47 -0
  98. data/lib/yoda/parsing/scopes/meta_class_definition.rb +44 -0
  99. data/lib/yoda/parsing/scopes/meta_method_definition.rb +70 -0
  100. data/lib/yoda/parsing/scopes/method_definition.rb +69 -0
  101. data/lib/yoda/parsing/scopes/module_definition.rb +36 -0
  102. data/lib/yoda/parsing/scopes/root.rb +25 -0
  103. data/lib/yoda/parsing/source_analyzer.rb +59 -0
  104. data/lib/yoda/parsing/source_cutter.rb +231 -0
  105. data/lib/yoda/parsing/type_parser.rb +141 -0
  106. data/lib/yoda/runner.rb +6 -0
  107. data/lib/yoda/runner/infer.rb +50 -0
  108. data/lib/yoda/runner/setup.rb +26 -0
  109. data/lib/yoda/server.rb +191 -0
  110. data/lib/yoda/server/client_info.rb +98 -0
  111. data/lib/yoda/server/completion_provider.rb +78 -0
  112. data/lib/yoda/server/definition_provider.rb +36 -0
  113. data/lib/yoda/server/deserializer.rb +27 -0
  114. data/lib/yoda/server/hover_provider.rb +38 -0
  115. data/lib/yoda/server/signature_provider.rb +46 -0
  116. data/lib/yoda/store.rb +13 -0
  117. data/lib/yoda/store/actions.rb +10 -0
  118. data/lib/yoda/store/actions/import_core_library.rb +30 -0
  119. data/lib/yoda/store/actions/import_gems.rb +91 -0
  120. data/lib/yoda/store/actions/read_file.rb +36 -0
  121. data/lib/yoda/store/actions/read_project_files.rb +29 -0
  122. data/lib/yoda/store/adapters.rb +14 -0
  123. data/lib/yoda/store/adapters/base.rb +58 -0
  124. data/lib/yoda/store/adapters/leveldb_adapter.rb +80 -0
  125. data/lib/yoda/store/adapters/lmdb_adapter.rb +113 -0
  126. data/lib/yoda/store/objects.rb +46 -0
  127. data/lib/yoda/store/objects/addressable.rb +25 -0
  128. data/lib/yoda/store/objects/base.rb +116 -0
  129. data/lib/yoda/store/objects/class_object.rb +51 -0
  130. data/lib/yoda/store/objects/merger.rb +94 -0
  131. data/lib/yoda/store/objects/meta_class_object.rb +41 -0
  132. data/lib/yoda/store/objects/method_object.rb +94 -0
  133. data/lib/yoda/store/objects/module_object.rb +11 -0
  134. data/lib/yoda/store/objects/namespace_object.rb +67 -0
  135. data/lib/yoda/store/objects/overload.rb +51 -0
  136. data/lib/yoda/store/objects/patch.rb +46 -0
  137. data/lib/yoda/store/objects/patch_set.rb +80 -0
  138. data/lib/yoda/store/objects/tag.rb +62 -0
  139. data/lib/yoda/store/objects/value_object.rb +45 -0
  140. data/lib/yoda/store/project.rb +159 -0
  141. data/lib/yoda/store/query.rb +12 -0
  142. data/lib/yoda/store/query/associators.rb +10 -0
  143. data/lib/yoda/store/query/associators/associate_ancestors.rb +103 -0
  144. data/lib/yoda/store/query/associators/associate_methods.rb +38 -0
  145. data/lib/yoda/store/query/base.rb +16 -0
  146. data/lib/yoda/store/query/find_constant.rb +150 -0
  147. data/lib/yoda/store/query/find_meta_class.rb +18 -0
  148. data/lib/yoda/store/query/find_method.rb +74 -0
  149. data/lib/yoda/store/query/find_signature.rb +43 -0
  150. data/lib/yoda/store/registry.rb +67 -0
  151. data/lib/yoda/store/yard_importer.rb +260 -0
  152. data/lib/yoda/typing.rb +10 -0
  153. data/lib/yoda/typing/context.rb +96 -0
  154. data/lib/yoda/typing/environment.rb +35 -0
  155. data/lib/yoda/typing/evaluator.rb +256 -0
  156. data/lib/yoda/typing/lexical_scope.rb +26 -0
  157. data/lib/yoda/typing/relation.rb +15 -0
  158. data/lib/yoda/typing/traces.rb +9 -0
  159. data/lib/yoda/typing/traces/base.rb +26 -0
  160. data/lib/yoda/typing/traces/normal.rb +22 -0
  161. data/lib/yoda/typing/traces/send.rb +26 -0
  162. data/lib/yoda/version.rb +3 -0
  163. data/lib/yoda/yard_extensions.rb +11 -0
  164. data/lib/yoda/yard_extensions/sig_directive.rb +40 -0
  165. data/lib/yoda/yard_extensions/type_tag.rb +10 -0
  166. data/package.json +76 -0
  167. data/scripts/benchmark.rb +6 -0
  168. data/scripts/build_core_index.sh +16 -0
  169. data/yarn.lock +13 -0
  170. data/yoda-language-server.gemspec +40 -0
  171. metadata +424 -0
@@ -0,0 +1,45 @@
1
+ module Yoda
2
+ module Store
3
+ module Objects
4
+ class ValueObject < Base
5
+ # @return [String]
6
+ attr_reader :value
7
+
8
+ # @return [Array<Symbol>]
9
+ def self.attr_names
10
+ super + %i(value)
11
+ end
12
+
13
+ # @param path [String]
14
+ # @param value [String]
15
+ def initialize(value: nil, **kwargs)
16
+ super(kwargs)
17
+ @value = value
18
+ end
19
+
20
+ # @return [String]
21
+ def name
22
+ @name ||= path.match(MODULE_TAIL_PATTERN) { |md| md[1] || md[2] }
23
+ end
24
+
25
+ def kind
26
+ :value
27
+ end
28
+
29
+ def to_h
30
+ super.merge(value: value)
31
+ end
32
+
33
+ private
34
+
35
+ # @param another [self]
36
+ # @return [Hash]
37
+ def merge_attributes(another)
38
+ super.merge(
39
+ value: another.value || self.value,
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,159 @@
1
+ require 'bundler'
2
+ require 'tmpdir'
3
+ require 'fileutils'
4
+ require 'digest'
5
+
6
+ module Yoda
7
+ module Store
8
+ class Project
9
+ # @type String
10
+ attr_reader :root_path
11
+
12
+ # @type Registry
13
+ attr_reader :registry
14
+
15
+ # @param root_path [String]
16
+ def initialize(root_path)
17
+ fail ArgumentError, root_path unless root_path.is_a?(String)
18
+
19
+ @root_path = File.absolute_path(root_path)
20
+ @registry = Registry.new
21
+ end
22
+
23
+ def clean
24
+ end
25
+
26
+ def setup
27
+ YARD::Logger.instance(STDERR)
28
+ make_dir
29
+ cache.setup
30
+ load_project_files
31
+ self
32
+ end
33
+
34
+ def rebuild_cache(progress: false)
35
+ make_dir
36
+ cache.build(progress: progress)
37
+ end
38
+
39
+ # @param source_path [String]
40
+ def read_source(source_path)
41
+ Actions::ReadFile.run(registry, source_path)
42
+ end
43
+
44
+ def yoda_dir
45
+ File.expand_path('.yoda', root_path)
46
+ end
47
+
48
+ private
49
+
50
+ def make_dir
51
+ File.exist?(yoda_dir) || FileUtils.mkdir(yoda_dir)
52
+ end
53
+
54
+ def load_project_files
55
+ Actions::ReadProjectFiles.new(registry, root_path).run
56
+ end
57
+
58
+ def cache
59
+ @cache ||= Cache.new(self)
60
+ end
61
+
62
+ class Cache
63
+ class Builder
64
+ # @return [Registry]
65
+ attr_reader :registry
66
+
67
+ # @return [String]
68
+ attr_reader :root_path
69
+
70
+ # @return [String]
71
+ attr_reader :gemfile_lock_path
72
+
73
+ def initialize(registry, root_path, gemfile_lock_path)
74
+ @registry = registry
75
+ @root_path = root_path
76
+ @gemfile_lock_path = gemfile_lock_path
77
+ end
78
+
79
+ def run(progress: false)
80
+ Actions::ImportCoreLibrary.new(registry).run
81
+ if File.exist?(gemfile_lock_path)
82
+ Actions::ImportGems.new(registry, gemfile_lock_parser.specs).run
83
+ end
84
+ registry.compress_and_save(progress: progress)
85
+ end
86
+
87
+ def gemfile_lock_parser
88
+ Dir.chdir(root_path) do
89
+ Bundler::LockfileParser.new(File.read(gemfile_lock_path))
90
+ end
91
+ end
92
+ end
93
+
94
+ # @return [Project]
95
+ attr_reader :project
96
+
97
+ def initialize(project)
98
+ @project = project
99
+ end
100
+
101
+ def build(progress: false)
102
+ STDERR.puts 'Constructing database for the current project.'
103
+ YARD::Logger.instance(STDERR)
104
+ make_cache_dir
105
+ register_adapter
106
+ project.registry.adapter.clear
107
+ Builder.new(project.registry, project.root_path, gemfile_lock_path).run(progress: progress)
108
+ STDERR.puts 'Finished to construct database for the current project.'
109
+ end
110
+
111
+ def setup
112
+ if present?
113
+ register_adapter
114
+ else
115
+ build
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ # @return [true, false]
122
+ def present?
123
+ File.exist?(cache_path)
124
+ end
125
+
126
+ def register_adapter
127
+ return if project.registry.adapter
128
+ project.registry.adapter = Adapters.default_adapter_class.for(cache_path)
129
+ end
130
+
131
+ def cache_dir
132
+ File.expand_path('cache', project.yoda_dir)
133
+ end
134
+
135
+ def cache_name
136
+ @cache_path ||= begin
137
+ digest = Digest::SHA256.new
138
+ digest.file(gemfile_lock_path) if File.exist?(gemfile_lock_path)
139
+ digest.update(Yoda::VERSION)
140
+ digest.update(Adapters.default_adapter_class.type.to_s)
141
+ digest.hexdigest
142
+ end
143
+ end
144
+
145
+ def cache_path
146
+ File.expand_path(cache_name, cache_dir)
147
+ end
148
+
149
+ def make_cache_dir
150
+ File.exist?(cache_dir) || FileUtils.mkdir_p(cache_dir)
151
+ end
152
+
153
+ def gemfile_lock_path
154
+ File.absolute_path('Gemfile.lock', project.root_path)
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,12 @@
1
+ module Yoda
2
+ module Store
3
+ module Query
4
+ require 'yoda/store/query/base'
5
+ require 'yoda/store/query/find_constant'
6
+ require 'yoda/store/query/find_meta_class'
7
+ require 'yoda/store/query/find_method'
8
+ require 'yoda/store/query/find_signature'
9
+ require 'yoda/store/query/associators'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ module Yoda
2
+ module Store
3
+ module Query
4
+ module Associators
5
+ require 'yoda/store/query/associators/associate_ancestors'
6
+ require 'yoda/store/query/associators/associate_methods'
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,103 @@
1
+ require 'set'
2
+
3
+ module Yoda
4
+ module Store
5
+ module Query
6
+ module Associators
7
+ class AssociateAncestors
8
+ class CircularReferenceError < StandardError; end
9
+
10
+ # @return [Registry]
11
+ attr_reader :registry
12
+
13
+ # @param registry [Registry]
14
+ def initialize(registry)
15
+ @registry = registry
16
+ end
17
+
18
+ # @param obj [Objects::Base]
19
+ def associate(obj)
20
+ if obj.is_a?(Objects::NamespaceObject)
21
+ obj.ancestors = Enumerator.new do |yielder|
22
+ Processor.new(registry).process(obj).each { |klass| yielder << klass }
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ class Processor
30
+ # @return [Registry]
31
+ attr_reader :registry
32
+
33
+ # @param registry [Registry]
34
+ def initialize(registry)
35
+ @registry = registry
36
+ end
37
+
38
+
39
+ # @return [Set<Objects::Base>]
40
+ def met
41
+ @met ||= Set.new
42
+ end
43
+
44
+ # @param scope [Objects::NamespaceObject]
45
+ # @return [Enumerator<Objects::NamespaceObject>]
46
+ def process(scope)
47
+ fail CircularReferenceError, scope if met.include?(scope)
48
+ met.add(scope)
49
+
50
+ Enumerator.new do |yielder|
51
+ if scope.is_a?(Objects::NamespaceObject)
52
+ yielder << scope
53
+ find_mixins(scope).each { |mixin| yielder << mixin }
54
+
55
+ if scope.is_a?(Objects::MetaClassObject)
56
+ find_metaclass_superclass_ancestors(scope).each { |obj| yielder << obj }
57
+ elsif scope.is_a?(Objects::ClassObject)
58
+ find_superclass_ancestors(scope).each { |obj| yielder << obj }
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ # @param obj [Objects::NamespaceObject]
65
+ # @return [Enumerator<Objects::NamespaceObject>]
66
+ def find_mixins(obj)
67
+ Enumerator.new do |yielder|
68
+ obj.mixin_addresses.each do |address|
69
+ if el = registry.find(address.to_s)
70
+ yielder << el
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ # @param obj [Objects::ClassObject]
77
+ # @return [Enumerator<Objects::NamespaceObject>]
78
+ def find_superclass_ancestors(obj)
79
+ if obj.superclass_path && super_class = FindConstant.new(registry).find(obj.superclass_path)
80
+ process(super_class)
81
+ else
82
+ []
83
+ end
84
+ end
85
+
86
+ # @param obj [Objects::MetaClassObject]
87
+ # @return [Enumerator<Objects::NamespaceObject>]
88
+ def find_metaclass_superclass_ancestors(obj)
89
+ base_class = registry.find(obj.base_class_address)
90
+ if base_class && base_class.is_a?(Objects::ClassObject) && base_class.superclass_path
91
+ (meta_class = FindMetaClass.new(registry).find(base_class.superclass_path || 'Object')) ? process(meta_class) : []
92
+ elsif base_class
93
+ (class_object = registry.find('Class')) ? process(class_object) : []
94
+ else
95
+ []
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,38 @@
1
+ module Yoda
2
+ module Store
3
+ module Query
4
+ module Associators
5
+ class AssociateMethods
6
+ # @return [Registry]
7
+ attr_reader :registry
8
+
9
+ # @param registry [Registry]
10
+ def initialize(registry)
11
+ @registry = registry
12
+ end
13
+
14
+ # @param obj [Object::Base]
15
+ def associate(obj)
16
+ if obj.is_a?(Objects::NamespaceObject)
17
+ AssociateMethods.new(registry).associate(obj)
18
+ obj.methods = Enumerator.new do |yielder|
19
+ name_set = Set.new
20
+
21
+ obj.ancestors.each do |ancestor|
22
+ ancestor.instance_method_addresses.each do |method_address|
23
+ method_name = Objects::MethodObject.name_of_path(method_address)
24
+ next name_set.has_key?(method_name)
25
+ name_set.add(method_name)
26
+ if el = registry.find(method_address)
27
+ yielder << el
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,16 @@
1
+ module Yoda
2
+ module Store
3
+ module Query
4
+ # @abstract
5
+ class Base
6
+ # @return [Registry]
7
+ attr_reader :registry
8
+
9
+ # @param registry [Registry]
10
+ def initialize(registry)
11
+ @registry = registry
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,150 @@
1
+ module Yoda
2
+ module Store
3
+ module Query
4
+ class FindConstant < Base
5
+ # @param path [String, Model::Path, Model::ScopedPath]
6
+ # @return [Objects::Base, nil]
7
+ def find(path)
8
+ lexical_scope_paths = lexical_scopes_of(path)
9
+ base_name, *constant_names = path_of(path).split
10
+ base_namespace = select_base_namespace(base_name, lexical_scope_paths).first
11
+
12
+ find_constant(constant_names.join('::'), base_namespace)
13
+ end
14
+
15
+ # @param path [String, Model::Path, Model::ScopedPath]
16
+ # @return [Array<Objects::Base>]
17
+ def select_with_prefix(path)
18
+ lexical_scope_paths = lexical_scopes_of(path)
19
+ base_name, *constant_names, bottom_name = path_of(path).split
20
+
21
+ if constant_names.empty? && !bottom_name
22
+ # When the path does not contain separator (`::`)
23
+ select_base_namespace(/\A#{Regexp.escape(base_name || '')}/, lexical_scope_paths).to_a.uniq
24
+ else
25
+ base_namespace = select_base_namespace(base_name, lexical_scope_paths).first
26
+ scope = find_constant(constant_names.join('::'), base_namespace)
27
+ return [] unless scope
28
+ select_constants_from_ancestors(scope, /\A#{bottom_name}/).to_a
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ # Find namespaces which matches with the lexical scopes and the unnested constant name.
35
+ #
36
+ # @param base_name [String, Regexp] is an unnested constant name or a pattern of unnested constant name.
37
+ # @param lexical_scope_paths [Array<String>]
38
+ # @return [Enumerator<Objects::Base>]
39
+ def select_base_namespace(base_name, lexical_scope_paths)
40
+ Enumerator.new do |yielder|
41
+ lexical_scope_paths.each do |path|
42
+ scope = find_constant(path.to_s)
43
+ next if !scope || !scope.is_a?(Objects::NamespaceObject)
44
+ select_child_constants(scope, base_name).each do |obj|
45
+ yielder << obj
46
+ end
47
+ end
48
+
49
+ nearest_scope_path = lexical_scope_paths.first
50
+ if nearest_scope_path && nearest_scope = find_constant(nearest_scope_path) && nearest_scope.is_a?(Objects::NamespaceObject)
51
+ select_constants_from_ancestors(nearest_scope, base_name).each do |obj|
52
+ yielder << obj
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ # @param name [String, Model::Path]
59
+ # @param namespace [Objects::Base, nil]
60
+ # @return [Objects::Base, nil]
61
+ def find_constant(name, namespace = nil)
62
+ constant_names = path_of(name).split
63
+ namespace = registry.find('Object') unless namespace
64
+ constant_names.reduce(namespace) do |namespace, name|
65
+ if namespace
66
+ select_constants_from_ancestors(namespace, name).first
67
+ else
68
+ return nil
69
+ end
70
+ end
71
+ end
72
+
73
+ # @param scope [Objects::NamespaceObject]
74
+ # @param name [String, Regexp]
75
+ # @return [Enumerator<Objects::Base>]
76
+ def select_constants_from_ancestors(scope, name)
77
+ Enumerator.new do |yielder|
78
+ met = Set.new
79
+
80
+ Associators::AssociateAncestors.new(registry).associate(scope)
81
+ scope.ancestors.each do |ancestor|
82
+ select_child_constants(ancestor, name).each do |obj|
83
+ next if met.include?(obj.name)
84
+ met.add(obj.name)
85
+ yielder << obj
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ # @param scope [Objects::Base]
92
+ # @param name [String, Regexp]
93
+ # @return [Enumerator<Objects::Base>]
94
+ def select_child_constants(scope, name)
95
+ Enumerator.new do |yielder|
96
+ if scope.is_a?(Objects::NamespaceObject)
97
+ scope.constant_addresses.select { |address| match_name?(Model::Path.new(address).basename, name) }.each do |address|
98
+ obj = registry.find(address)
99
+ yielder << obj if obj
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ # @param path [String, Model::Path, Model::ScopedPath]
106
+ # @return [Array<String>]
107
+ def lexical_scopes_of(path)
108
+ case path
109
+ when Model::Path
110
+ ['Object']
111
+ when Model::ScopedPath
112
+ if path.path.absolute?
113
+ ['Object']
114
+ else
115
+ path.scopes.map { |scope| scope.to_s.gsub(/\A::/, '') }
116
+ end
117
+ else
118
+ ['Object']
119
+ end
120
+ end
121
+
122
+ # @param path [String, Model::Path, Model::ScopedPath]
123
+ # @return [Model::Path]
124
+ def path_of(path)
125
+ case path
126
+ when Model::Path
127
+ path
128
+ when Model::ScopedPath
129
+ path.path
130
+ when String
131
+ Model::Path.new(path == '::' ? '::' : path.gsub(/\A::/, ''))
132
+ else
133
+ fail ArgumentError, path
134
+ end
135
+ end
136
+
137
+ # @param name [String]
138
+ # @param expected_name_or_pattern [String, Regexp]
139
+ # @return [true, false]
140
+ def match_name?(name, expected_name_or_pattern)
141
+ if expected_name_or_pattern.is_a?(String)
142
+ name == expected_name_or_pattern
143
+ else
144
+ name.match?(expected_name_or_pattern)
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end