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.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.travis.yml +8 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +78 -0
- data/LICENSE.txt +21 -0
- data/README.md +85 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/client/atom/main.js +27 -0
- data/client/vscode/.gitignore +4 -0
- data/client/vscode/.vscode/launch.json +28 -0
- data/client/vscode/.vscode/settings.json +9 -0
- data/client/vscode/.vscode/tasks.json +20 -0
- data/client/vscode/.vscodeignore +8 -0
- data/client/vscode/CHANGELOG.md +7 -0
- data/client/vscode/README.md +65 -0
- data/client/vscode/package-lock.json +2688 -0
- data/client/vscode/package.json +39 -0
- data/client/vscode/src/extension.ts +42 -0
- data/client/vscode/src/test/extension.test.ts +22 -0
- data/client/vscode/src/test/index.ts +22 -0
- data/client/vscode/tsconfig.json +16 -0
- data/client/vscode/vsc-extension-quickstart.md +33 -0
- data/exe/yoda +27 -0
- data/lib/yoda.rb +11 -0
- data/lib/yoda/evaluation.rb +9 -0
- data/lib/yoda/evaluation/code_completion.rb +65 -0
- data/lib/yoda/evaluation/code_completion/base_provider.rb +57 -0
- data/lib/yoda/evaluation/code_completion/const_provider.rb +90 -0
- data/lib/yoda/evaluation/code_completion/method_provider.rb +82 -0
- data/lib/yoda/evaluation/code_completion/variable_provider.rb +18 -0
- data/lib/yoda/evaluation/comment_completion.rb +70 -0
- data/lib/yoda/evaluation/comment_completion/base_provider.rb +64 -0
- data/lib/yoda/evaluation/comment_completion/param_provider.rb +18 -0
- data/lib/yoda/evaluation/comment_completion/tag_provider.rb +41 -0
- data/lib/yoda/evaluation/comment_completion/type_provider.rb +58 -0
- data/lib/yoda/evaluation/current_node_explain.rb +70 -0
- data/lib/yoda/evaluation/evaluator.rb +103 -0
- data/lib/yoda/evaluation/signature_discovery.rb +83 -0
- data/lib/yoda/model.rb +12 -0
- data/lib/yoda/model/completion_item.rb +56 -0
- data/lib/yoda/model/descriptions.rb +10 -0
- data/lib/yoda/model/descriptions/base.rb +26 -0
- data/lib/yoda/model/descriptions/function_description.rb +40 -0
- data/lib/yoda/model/descriptions/value_description.rb +33 -0
- data/lib/yoda/model/descriptions/word_description.rb +32 -0
- data/lib/yoda/model/function_signatures.rb +13 -0
- data/lib/yoda/model/function_signatures/base.rb +68 -0
- data/lib/yoda/model/function_signatures/constructor.rb +70 -0
- data/lib/yoda/model/function_signatures/formatter.rb +82 -0
- data/lib/yoda/model/function_signatures/method.rb +67 -0
- data/lib/yoda/model/function_signatures/overload.rb +79 -0
- data/lib/yoda/model/function_signatures/parameter_list.rb +108 -0
- data/lib/yoda/model/function_signatures/type_builder.rb +101 -0
- data/lib/yoda/model/node_signature.rb +28 -0
- data/lib/yoda/model/path.rb +96 -0
- data/lib/yoda/model/scoped_path.rb +44 -0
- data/lib/yoda/model/types.rb +84 -0
- data/lib/yoda/model/types/any_type.rb +32 -0
- data/lib/yoda/model/types/base.rb +37 -0
- data/lib/yoda/model/types/duck_type.rb +41 -0
- data/lib/yoda/model/types/function_type.rb +174 -0
- data/lib/yoda/model/types/generic_type.rb +66 -0
- data/lib/yoda/model/types/instance_type.rb +42 -0
- data/lib/yoda/model/types/module_type.rb +42 -0
- data/lib/yoda/model/types/sequence_type.rb +53 -0
- data/lib/yoda/model/types/union_type.rb +56 -0
- data/lib/yoda/model/types/unknown_type.rb +40 -0
- data/lib/yoda/model/types/value_type.rb +58 -0
- data/lib/yoda/model/values.rb +9 -0
- data/lib/yoda/model/values/base.rb +32 -0
- data/lib/yoda/model/values/instance_value.rb +65 -0
- data/lib/yoda/model/values/module_value.rb +72 -0
- data/lib/yoda/parsing.rb +15 -0
- data/lib/yoda/parsing/ast_traversable.rb +18 -0
- data/lib/yoda/parsing/comment_tokenizer.rb +59 -0
- data/lib/yoda/parsing/location.rb +101 -0
- data/lib/yoda/parsing/node_objects.rb +10 -0
- data/lib/yoda/parsing/node_objects/const_node.rb +52 -0
- data/lib/yoda/parsing/node_objects/method_definition.rb +46 -0
- data/lib/yoda/parsing/node_objects/namespace.rb +104 -0
- data/lib/yoda/parsing/node_objects/send_node.rb +72 -0
- data/lib/yoda/parsing/parser.rb +27 -0
- data/lib/yoda/parsing/query.rb +11 -0
- data/lib/yoda/parsing/query/current_comment_query.rb +80 -0
- data/lib/yoda/parsing/query/current_comment_token_query.rb +153 -0
- data/lib/yoda/parsing/query/current_commenting_node_query.rb +68 -0
- data/lib/yoda/parsing/query/current_location_node_query.rb +51 -0
- data/lib/yoda/parsing/query/current_node_comment_query.rb +40 -0
- data/lib/yoda/parsing/range.rb +41 -0
- data/lib/yoda/parsing/scopes.rb +15 -0
- data/lib/yoda/parsing/scopes/base.rb +78 -0
- data/lib/yoda/parsing/scopes/builder.rb +60 -0
- data/lib/yoda/parsing/scopes/class_definition.rb +47 -0
- data/lib/yoda/parsing/scopes/meta_class_definition.rb +44 -0
- data/lib/yoda/parsing/scopes/meta_method_definition.rb +70 -0
- data/lib/yoda/parsing/scopes/method_definition.rb +69 -0
- data/lib/yoda/parsing/scopes/module_definition.rb +36 -0
- data/lib/yoda/parsing/scopes/root.rb +25 -0
- data/lib/yoda/parsing/source_analyzer.rb +59 -0
- data/lib/yoda/parsing/source_cutter.rb +231 -0
- data/lib/yoda/parsing/type_parser.rb +141 -0
- data/lib/yoda/runner.rb +6 -0
- data/lib/yoda/runner/infer.rb +50 -0
- data/lib/yoda/runner/setup.rb +26 -0
- data/lib/yoda/server.rb +191 -0
- data/lib/yoda/server/client_info.rb +98 -0
- data/lib/yoda/server/completion_provider.rb +78 -0
- data/lib/yoda/server/definition_provider.rb +36 -0
- data/lib/yoda/server/deserializer.rb +27 -0
- data/lib/yoda/server/hover_provider.rb +38 -0
- data/lib/yoda/server/signature_provider.rb +46 -0
- data/lib/yoda/store.rb +13 -0
- data/lib/yoda/store/actions.rb +10 -0
- data/lib/yoda/store/actions/import_core_library.rb +30 -0
- data/lib/yoda/store/actions/import_gems.rb +91 -0
- data/lib/yoda/store/actions/read_file.rb +36 -0
- data/lib/yoda/store/actions/read_project_files.rb +29 -0
- data/lib/yoda/store/adapters.rb +14 -0
- data/lib/yoda/store/adapters/base.rb +58 -0
- data/lib/yoda/store/adapters/leveldb_adapter.rb +80 -0
- data/lib/yoda/store/adapters/lmdb_adapter.rb +113 -0
- data/lib/yoda/store/objects.rb +46 -0
- data/lib/yoda/store/objects/addressable.rb +25 -0
- data/lib/yoda/store/objects/base.rb +116 -0
- data/lib/yoda/store/objects/class_object.rb +51 -0
- data/lib/yoda/store/objects/merger.rb +94 -0
- data/lib/yoda/store/objects/meta_class_object.rb +41 -0
- data/lib/yoda/store/objects/method_object.rb +94 -0
- data/lib/yoda/store/objects/module_object.rb +11 -0
- data/lib/yoda/store/objects/namespace_object.rb +67 -0
- data/lib/yoda/store/objects/overload.rb +51 -0
- data/lib/yoda/store/objects/patch.rb +46 -0
- data/lib/yoda/store/objects/patch_set.rb +80 -0
- data/lib/yoda/store/objects/tag.rb +62 -0
- data/lib/yoda/store/objects/value_object.rb +45 -0
- data/lib/yoda/store/project.rb +159 -0
- data/lib/yoda/store/query.rb +12 -0
- data/lib/yoda/store/query/associators.rb +10 -0
- data/lib/yoda/store/query/associators/associate_ancestors.rb +103 -0
- data/lib/yoda/store/query/associators/associate_methods.rb +38 -0
- data/lib/yoda/store/query/base.rb +16 -0
- data/lib/yoda/store/query/find_constant.rb +150 -0
- data/lib/yoda/store/query/find_meta_class.rb +18 -0
- data/lib/yoda/store/query/find_method.rb +74 -0
- data/lib/yoda/store/query/find_signature.rb +43 -0
- data/lib/yoda/store/registry.rb +67 -0
- data/lib/yoda/store/yard_importer.rb +260 -0
- data/lib/yoda/typing.rb +10 -0
- data/lib/yoda/typing/context.rb +96 -0
- data/lib/yoda/typing/environment.rb +35 -0
- data/lib/yoda/typing/evaluator.rb +256 -0
- data/lib/yoda/typing/lexical_scope.rb +26 -0
- data/lib/yoda/typing/relation.rb +15 -0
- data/lib/yoda/typing/traces.rb +9 -0
- data/lib/yoda/typing/traces/base.rb +26 -0
- data/lib/yoda/typing/traces/normal.rb +22 -0
- data/lib/yoda/typing/traces/send.rb +26 -0
- data/lib/yoda/version.rb +3 -0
- data/lib/yoda/yard_extensions.rb +11 -0
- data/lib/yoda/yard_extensions/sig_directive.rb +40 -0
- data/lib/yoda/yard_extensions/type_tag.rb +10 -0
- data/package.json +76 -0
- data/scripts/benchmark.rb +6 -0
- data/scripts/build_core_index.sh +16 -0
- data/yarn.lock +13 -0
- data/yoda-language-server.gemspec +40 -0
- 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,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,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
|