yoda-language-server 0.4.0 → 0.5.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +5 -3
  3. data/README.md +50 -48
  4. data/client/atom/main.js +13 -3
  5. data/client/vscode/.vscode/launch.json +7 -4
  6. data/client/vscode/package-lock.json +585 -1454
  7. data/client/vscode/package.json +10 -7
  8. data/client/vscode/src/extension.ts +3 -3
  9. data/client/vscode/src/test/completion.test.ts +39 -0
  10. data/client/vscode/src/test/helper.ts +38 -0
  11. data/client/vscode/src/test/index.ts +5 -3
  12. data/client/vscode/testFixture/completion.rb +1 -0
  13. data/exe/yoda +1 -20
  14. data/lib/yoda.rb +2 -1
  15. data/lib/yoda/commands.rb +34 -0
  16. data/lib/yoda/commands/base.rb +10 -0
  17. data/lib/yoda/commands/complete.rb +36 -0
  18. data/lib/yoda/commands/file_cursor_parsable.rb +29 -0
  19. data/lib/yoda/{runner → commands}/infer.rb +4 -9
  20. data/lib/yoda/commands/setup.rb +37 -0
  21. data/lib/yoda/errors.rb +34 -0
  22. data/lib/yoda/evaluation/evaluator.rb +2 -0
  23. data/lib/yoda/server.rb +60 -15
  24. data/lib/yoda/server/completion_provider.rb +8 -8
  25. data/lib/yoda/server/definition_provider.rb +8 -8
  26. data/lib/yoda/server/hover_provider.rb +6 -6
  27. data/lib/yoda/server/initialization_provider.rb +85 -0
  28. data/lib/yoda/server/{client_info.rb → session.rb} +6 -2
  29. data/lib/yoda/server/signature_provider.rb +6 -6
  30. data/lib/yoda/store/actions.rb +3 -1
  31. data/lib/yoda/store/actions/build_core_index.rb +44 -0
  32. data/lib/yoda/store/actions/import_core_library.rb +14 -9
  33. data/lib/yoda/store/actions/import_gem.rb +98 -0
  34. data/lib/yoda/store/actions/import_std_library.rb +35 -0
  35. data/lib/yoda/store/adapters.rb +1 -0
  36. data/lib/yoda/store/adapters/memory_adapter.rb +81 -0
  37. data/lib/yoda/store/objects.rb +4 -0
  38. data/lib/yoda/store/objects/addressable.rb +0 -12
  39. data/lib/yoda/store/objects/base.rb +4 -14
  40. data/lib/yoda/store/objects/merger.rb +4 -4
  41. data/lib/yoda/store/objects/patchable.rb +19 -0
  42. data/lib/yoda/store/objects/project_status.rb +169 -0
  43. data/lib/yoda/store/objects/serializable.rb +39 -0
  44. data/lib/yoda/store/project.rb +28 -114
  45. data/lib/yoda/store/project/cache.rb +79 -0
  46. data/lib/yoda/store/project/library_doc_loader.rb +102 -0
  47. data/lib/yoda/store/query/find_constant.rb +2 -1
  48. data/lib/yoda/store/registry.rb +15 -0
  49. data/lib/yoda/store/yard_importer.rb +58 -28
  50. data/lib/yoda/typing/evaluator.rb +8 -5
  51. data/lib/yoda/version.rb +1 -1
  52. data/package.json +32 -11
  53. data/scripts/benchmark.rb +1 -1
  54. data/yoda-language-server.gemspec +1 -0
  55. metadata +37 -7
  56. data/client/vscode/src/test/extension.test.ts +0 -22
  57. data/lib/yoda/runner/setup.rb +0 -26
  58. data/lib/yoda/store/actions/import_gems.rb +0 -91
@@ -1,11 +1,11 @@
1
- require 'bundler'
2
- require 'tmpdir'
3
1
  require 'fileutils'
4
- require 'digest'
5
2
 
6
3
  module Yoda
7
4
  module Store
8
5
  class Project
6
+ require 'yoda/store/project/cache'
7
+ require 'yoda/store/project/library_doc_loader'
8
+
9
9
  # @type String
10
10
  attr_reader :root_path
11
11
 
@@ -20,20 +20,33 @@ module Yoda
20
20
  @registry = Registry.new
21
21
  end
22
22
 
23
- def clean
24
- end
25
-
26
23
  def setup
27
24
  YARD::Logger.instance(STDERR)
28
25
  make_dir
29
- cache.setup
26
+ cache.register_adapter(registry)
27
+ end
28
+
29
+ def clear
30
+ setup
31
+ registry.adapter.clear
32
+ end
33
+
34
+ # @return [Array<BaseError>]
35
+ def build_cache(progress: false)
36
+ setup
37
+ loader = LibraryDocLoader.build_for(self)
38
+ loader.run(progress: progress)
30
39
  load_project_files
31
- self
40
+ loader.errors
32
41
  end
33
42
 
34
43
  def rebuild_cache(progress: false)
35
- make_dir
36
- cache.build(progress: progress)
44
+ clear
45
+ build_cache(progress: progress)
46
+ end
47
+
48
+ def yoda_dir
49
+ File.expand_path('.yoda', root_path)
37
50
  end
38
51
 
39
52
  # @param source_path [String]
@@ -41,118 +54,19 @@ module Yoda
41
54
  Actions::ReadFile.run(registry, source_path)
42
55
  end
43
56
 
44
- def yoda_dir
45
- File.expand_path('.yoda', root_path)
46
- end
47
-
48
57
  private
49
58
 
50
- def make_dir
51
- File.exist?(yoda_dir) || FileUtils.mkdir(yoda_dir)
52
- end
53
-
54
59
  def load_project_files
55
60
  Actions::ReadProjectFiles.new(registry, root_path).run
56
61
  end
57
62
 
58
- def cache
59
- @cache ||= Cache.new(self)
63
+ def make_dir
64
+ File.exist?(yoda_dir) || FileUtils.mkdir(yoda_dir)
60
65
  end
61
66
 
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
67
+ # @return [Cache]
68
+ def cache
69
+ @cache ||= Cache.build_for(self)
156
70
  end
157
71
  end
158
72
  end
@@ -0,0 +1,79 @@
1
+ require 'fileutils'
2
+ require 'bundler'
3
+ require 'tmpdir'
4
+ require 'digest'
5
+
6
+ module Yoda
7
+ module Store
8
+ class Project
9
+ # Find registry file for the current project settings.
10
+ class Cache
11
+ class << self
12
+ # @param project_dir [String]
13
+ # @return [String]
14
+ def cache_dir(project_dir)
15
+ File.expand_path('.yoda/cache', project_dir)
16
+ end
17
+
18
+ # @param project_dir [String]
19
+ # @return [String]
20
+ def gemfile_lock_path(project_dir)
21
+ File.absolute_path('Gemfile.lock', project_dir)
22
+ end
23
+
24
+ # @param project [Project]
25
+ def build_for(project)
26
+ new(cache_dir_path: cache_dir(project.root_path), gemfile_lock_path: gemfile_lock_path(project.root_path))
27
+ end
28
+ end
29
+
30
+ # @return [String]
31
+ attr_reader :cache_dir_path
32
+
33
+ # @return [String, nil]
34
+ attr_reader :gemfile_lock_path
35
+
36
+ # @param cache_dir_path [String]
37
+ # @param gemfile_lock_path [String, nil]
38
+ def initialize(cache_dir_path:, gemfile_lock_path: nil)
39
+ @cache_dir_path = cache_dir_path
40
+ @gemfile_lock_path = gemfile_lock_path
41
+ end
42
+
43
+ # @return [true, false]
44
+ def present?
45
+ File.exist?(cache_path)
46
+ end
47
+
48
+ # @param registry [Registry]
49
+ def register_adapter(registry)
50
+ return if registry.adapter
51
+ make_cache_dir
52
+ registry.adapter = Adapters.default_adapter_class.for(cache_path)
53
+ end
54
+
55
+ # @return [String]
56
+ def cache_path
57
+ File.expand_path(cache_name, cache_dir_path)
58
+ end
59
+
60
+ private
61
+
62
+ # @return [String]
63
+ def cache_name
64
+ @cache_path ||= begin
65
+ digest = Digest::SHA256.new
66
+ digest.file(gemfile_lock_path) if gemfile_lock_path && File.exist?(gemfile_lock_path)
67
+ digest.update(Registry::REGISTRY_VERSION.to_s)
68
+ digest.update(Adapters.default_adapter_class.type.to_s)
69
+ digest.hexdigest
70
+ end
71
+ end
72
+
73
+ def make_cache_dir
74
+ File.exist?(cache_dir_path) || FileUtils.mkdir_p(cache_dir_path)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,102 @@
1
+ module Yoda
2
+ module Store
3
+ class Project
4
+ class LibraryDocLoader
5
+ # @return [Registry]
6
+ attr_reader :registry
7
+
8
+ # @param gem_specs [Array<Objects::ProjectStatus::GemStatus, Bundler::LazySpecification>]
9
+ attr_reader :gem_specs
10
+
11
+ # @param errors [Array<BaseError>]
12
+ attr_reader :errors
13
+
14
+ class << self
15
+ # @param project [Project]
16
+ # @return [LibraryDocLoader]
17
+ def build_for(project)
18
+ lockfile_parser = parse_gemfile_lock(project.root_path, Cache.gemfile_lock_path(project.root_path))
19
+ new(registry: project.registry, gem_specs: lockfile_parser&.specs || [])
20
+ end
21
+
22
+ private
23
+
24
+ # @return [Bundler::LockfileParser, nil]
25
+ def parse_gemfile_lock(root_path, gemfile_lock_path)
26
+ return if !gemfile_lock_path || !File.exists?(gemfile_lock_path)
27
+ Dir.chdir(root_path) do
28
+ Bundler::LockfileParser.new(File.read(gemfile_lock_path))
29
+ end
30
+ end
31
+ end
32
+
33
+ # @param registry [Registry]
34
+ # @param gem_specs [Array<Objects::ProjectStatus::GemStatus, Bundler::LazySpecification>]
35
+ def initialize(registry:, gem_specs:)
36
+ @registry = registry
37
+ @gem_specs = gem_specs
38
+ @errors = []
39
+ end
40
+
41
+ def run(progress: false)
42
+ project_status = registry.project_status || Objects::ProjectStatus.initial_build(specs: gem_specs)
43
+ new_bundle_status = update_bundle(project_status.bundle, progress: progress)
44
+ registry.save_project_status(project_status.derive(bundle: new_bundle_status))
45
+ end
46
+
47
+ private
48
+
49
+ # @param bundle_status [Objects::ProjectStatus::BundleStatus]
50
+ # @return [Objects::ProjectStatus::BundleStatus]
51
+ def update_bundle(bundle_status, progress: false)
52
+ unless bundle_status.all_present?
53
+ STDERR.puts 'Constructing database for the current project.'
54
+ bundle_status = import_deps(bundle_status)
55
+ registry.compress_and_save(progress: progress)
56
+ end
57
+ bundle_status
58
+ end
59
+
60
+ # Try to import missing gems and core libraries.
61
+ # @param bundle_status [Objects::ProjectStatus::BundleStatus]
62
+ # @return [Objects::ProjectStatus::BundleStatus]
63
+ def import_deps(bundle_status)
64
+ bundle_status = import_core(bundle_status) unless bundle_status.std_status.core_present?
65
+ bundle_status = import_std(bundle_status) unless bundle_status.std_status.std_present?
66
+ import_gems(bundle_status)
67
+ end
68
+
69
+ # @param bundle_status [Objects::ProjectStatus::BundleStatus]
70
+ # @return [Objects::ProjectStatus::BundleStatus]
71
+ def import_core(bundle_status)
72
+ result = Actions::ImportCoreLibrary.run(registry)
73
+ errors.push(CoreImportError.new('core')) unless result
74
+ bundle_status.derive(std_status: bundle_status.std_status.derive(core_present: !!result))
75
+ end
76
+
77
+ # @param bundle_status [Objects::ProjectStatus::BundleStatus]
78
+ # @return [Objects::ProjectStatus::BundleStatus]
79
+ def import_std(bundle_status)
80
+ result = Actions::ImportStdLibrary.run(registry)
81
+ errors.push(CoreImportError.new('std')) unless result
82
+ bundle_status.derive(std_status: bundle_status.std_status.derive(std_present: !!result))
83
+ end
84
+
85
+ # @param bundle_status [Objects::ProjectStatus::BundleStatus]
86
+ # @return [Objects::ProjectStatus::BundleStatus]
87
+ def import_gems(bundle_status)
88
+ gem_statuses = bundle_status.gem_statuses.map do |gem_status|
89
+ if gem_status.present?
90
+ gem_status
91
+ else
92
+ result = Actions::ImportGem.run(registry: registry, gem_name: gem_status.name, gem_version: gem_status.version)
93
+ errors.push(GemImportError.new(name: gem_status.name, version: gem_status.version)) unless result
94
+ gem_status.derive(present: result)
95
+ end
96
+ end
97
+ bundle_status.derive(gem_statuses: gem_statuses)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -62,7 +62,8 @@ module Yoda
62
62
  constant_names = path_of(name).split
63
63
  namespace = registry.find('Object') unless namespace
64
64
  constant_names.reduce(namespace) do |namespace, name|
65
- if namespace
65
+ # @todo resolve its value if namespace is ValueObject
66
+ if namespace && namespace.is_a?(Objects::NamespaceObject)
66
67
  select_constants_from_ancestors(namespace, name).first
67
68
  else
68
69
  return nil
@@ -4,6 +4,9 @@ require 'ruby-progressbar'
4
4
  module Yoda
5
5
  module Store
6
6
  class Registry
7
+ # @note This number must be updated when breaking change is added.
8
+ REGISTRY_VERSION = 1
9
+
7
10
  # @return [Adapters::LmdbAdapter, nil]
8
11
  attr_reader :adapter
9
12
 
@@ -13,11 +16,23 @@ module Yoda
13
16
  # @return [Objects::PatchSet]
14
17
  attr_reader :patch_set
15
18
 
19
+ PROJECT_STATUS_KEY = '%project_status'
20
+
16
21
  def initialize(adapter = nil)
17
22
  @patch_set = Objects::PatchSet.new
18
23
  @adapter = adapter
19
24
  end
20
25
 
26
+ # @return [Objects::ProjectStatus, nil]
27
+ def project_status
28
+ adapter&.exist?(PROJECT_STATUS_KEY) && adapter.get(PROJECT_STATUS_KEY)
29
+ end
30
+
31
+ # @param new_project_status [Objects::ProjectStatus]
32
+ def save_project_status(new_project_status)
33
+ adapter.put(PROJECT_STATUS_KEY, new_project_status)
34
+ end
35
+
21
36
  # @param path [String]
22
37
  # @return [Objects::Base, nil]
23
38
  def find(path)
@@ -73,26 +73,33 @@ module Yoda
73
73
  # @param code_object [::YARD::CodeObjects::NamespaceObject]
74
74
  # @return [Objects::ClassObject]
75
75
  def convert_root_object(code_object)
76
- # @todo Add meta class for main object.
77
- Objects::ClassObject.new(
78
- path: 'Object',
76
+ object_class = Objects::ClassObject.new(
77
+ path: path_to_store(code_object),
79
78
  document: code_object.docstring.to_s,
80
79
  tag_list: code_object.tags.map { |tag| convert_tag(tag, '') },
81
80
  sources: code_object.files.map(&method(:convert_source)),
82
81
  primary_source: code_object[:current_file_has_comments] ? convert_source(code_object.files.first) : nil,
83
82
  instance_method_addresses: code_object.meths(included: false, scope: :instance).map(&:path),
84
- mixin_addresses: code_object.instance_mixins.map { |mixin| mixin.path },
83
+ mixin_addresses: code_object.instance_mixins.map { |mixin| path_to_store(mixin) },
85
84
  constant_addresses: (code_object.children.select{ |child| %i(constant module class).include?(child.type) }.map { |constant| constant.path } + ['Object']).uniq,
86
85
  )
86
+ object_meta_class = Objects::MetaClassObject.new(
87
+ path: path_to_store(code_object),
88
+ sources: code_object.files.map(&method(:convert_source)),
89
+ primary_source: code_object[:current_file_has_comments] ? convert_source(code_object.files.first) : nil,
90
+ instance_method_addresses: code_object.meths(included: false, scope: :class).map(&:path),
91
+ mixin_addresses: code_object.instance_mixins.map { |mixin| path_to_store(mixin) },
92
+ )
93
+ [object_class, object_meta_class]
87
94
  end
88
95
 
89
96
  # @param code_object [::YARD::CodeObjects::ConstantObject]
90
97
  # @return [Objects::ValueObject]
91
98
  def convert_constant_object(code_object)
92
99
  Objects::ValueObject.new(
93
- path: code_object.path,
100
+ path: path_to_store(code_object),
94
101
  document: code_object.docstring.to_s,
95
- tag_list: code_object.tags.map { |tag| convert_tag(tag, code_object.namespace.path) },
102
+ tag_list: code_object.tags.map { |tag| convert_tag(tag, path_to_store(code_object.namespace)) },
96
103
  sources: code_object.files.map(&method(:convert_source)),
97
104
  primary_source: code_object[:current_file_has_comments] ? convert_source(code_object.files.first) : nil,
98
105
  value: code_object.value,
@@ -107,8 +114,8 @@ module Yoda
107
114
  method_object = Objects::MethodObject.new(
108
115
  path: "Object#{code_object.sep}#{code_object.name}",
109
116
  document: code_object.docstring.to_s,
110
- tag_list: code_object.tags.map { |tag| convert_tag(tag, code_object.namespace.path) },
111
- overloads: code_object.tags(:overload).map { |tag| convert_overload_tag(tag, code_object.namespace.path) },
117
+ tag_list: code_object.tags.map { |tag| convert_tag(tag, path_to_store(code_object.namespace)) },
118
+ overloads: code_object.tags(:overload).map { |tag| convert_overload_tag(tag, path_to_store(code_object.namespace)) },
112
119
  sources: code_object.files.map(&method(:convert_source)),
113
120
  primary_source: code_object[:current_file_has_comments] ? convert_source(code_object.files.first) : nil,
114
121
  parameters: code_object.parameters,
@@ -121,10 +128,10 @@ module Yoda
121
128
  [method_object, object_object]
122
129
  else
123
130
  Objects::MethodObject.new(
124
- path: code_object.path,
131
+ path: path_to_store(code_object),
125
132
  document: code_object.docstring.to_s,
126
- tag_list: code_object.tags.map { |tag| convert_tag(tag, code_object.namespace.path) },
127
- overloads: code_object.tags(:overload).map { |tag| convert_overload_tag(tag, code_object.namespace.path) },
133
+ tag_list: code_object.tags.map { |tag| convert_tag(tag, path_to_store(code_object.namespace)) },
134
+ overloads: code_object.tags(:overload).map { |tag| convert_overload_tag(tag, path_to_store(code_object.namespace)) },
128
135
  sources: code_object.files.map(&method(:convert_source)),
129
136
  primary_source: code_object[:current_file_has_comments] ? convert_source(code_object.files.first) : nil,
130
137
  parameters: code_object.parameters,
@@ -137,22 +144,22 @@ module Yoda
137
144
  # @return [Array<Objects::ModuleObject, Objects::MetaClassObject>]
138
145
  def convert_module_object(code_object)
139
146
  module_object = Objects::ModuleObject.new(
140
- path: code_object.path,
147
+ path: path_to_store(code_object),
141
148
  document: code_object.docstring.to_s,
142
- tag_list: code_object.tags.map { |tag| convert_tag(tag, code_object.path) },
149
+ tag_list: code_object.tags.map { |tag| convert_tag(tag, path_to_store(code_object)) },
143
150
  sources: code_object.files.map(&method(:convert_source)),
144
151
  primary_source: code_object[:current_file_has_comments] ? convert_source(code_object.files.first) : nil,
145
152
  instance_method_addresses: code_object.meths(included: false, scope: :instance).map(&:path),
146
- mixin_addresses: code_object.instance_mixins.map { |mixin| mixin.path },
153
+ mixin_addresses: code_object.instance_mixins.map { |mixin| path_to_store(mixin) },
147
154
  constant_addresses: code_object.children.select{ |child| %i(constant module class).include?(child.type) }.map { |constant| constant.path },
148
155
  )
149
156
 
150
157
  meta_class_object = Objects::MetaClassObject.new(
151
- path: code_object.path,
158
+ path: path_to_store(code_object),
152
159
  sources: code_object.files.map(&method(:convert_source)),
153
160
  primary_source: code_object[:current_file_has_comments] ? convert_source(code_object.files.first) : nil,
154
161
  instance_method_addresses: code_object.meths(included: false, scope: :class).map(&:path),
155
- mixin_addresses: code_object.instance_mixins.map { |mixin| mixin.path },
162
+ mixin_addresses: code_object.instance_mixins.map { |mixin| path_to_store(mixin) },
156
163
  )
157
164
 
158
165
  [module_object, meta_class_object]
@@ -162,23 +169,23 @@ module Yoda
162
169
  # @return [Array<Objects::ClassObject, Objects::MetaClassObject>]
163
170
  def convert_class_object(code_object)
164
171
  class_object = Objects::ClassObject.new(
165
- path: code_object.path,
172
+ path: path_to_store(code_object),
166
173
  document: code_object.docstring.to_s,
167
- tag_list: code_object.tags.map { |tag| convert_tag(tag, code_object.path) },
174
+ tag_list: code_object.tags.map { |tag| convert_tag(tag, path_to_store(code_object)) },
168
175
  sources: code_object.files.map(&method(:convert_source)),
169
176
  primary_source: code_object[:current_file_has_comments] ? convert_source(code_object.files.first) : nil,
170
177
  instance_method_addresses: code_object.meths(included: false, scope: :instance).map(&:path),
171
- mixin_addresses: code_object.instance_mixins.map { |mixin| mixin.path },
172
- constant_addresses: code_object.children.select{ |child| %i(constant module class).include?(child.type) }.map { |constant| constant.path },
173
- superclass_path: code_object.superclass&.path == 'Qnil' ? nil : code_object.superclass&.path,
178
+ mixin_addresses: code_object.instance_mixins.map { |mixin| path_to_store(mixin) },
179
+ constant_addresses: code_object.children.select{ |child| %i(constant module class).include?(child.type) }.map { |constant| path_to_store(constant) },
180
+ superclass_path: !code_object.superclass || code_object.superclass&.path == 'Qnil' ? nil : path_to_store(code_object.superclass),
174
181
  )
175
182
 
176
183
  meta_class_object = Objects::MetaClassObject.new(
177
- path: code_object.path,
184
+ path: path_to_store(code_object),
178
185
  sources: code_object.files.map(&method(:convert_source)),
179
186
  primary_source: code_object[:current_file_has_comments] ? convert_source(code_object.files.first) : nil,
180
187
  instance_method_addresses: code_object.meths(included: false, scope: :class).map(&:path),
181
- mixin_addresses: code_object.class_mixins.map { |mixin| mixin.path },
188
+ mixin_addresses: code_object.class_mixins.map { |mixin| path_to_store(mixin) },
182
189
  )
183
190
 
184
191
  [class_object, meta_class_object]
@@ -220,7 +227,7 @@ module Yoda
220
227
  # @return [Array<Objects::ModuleObject>]
221
228
  def create_proxy_module(code_object)
222
229
  module_object = Objects::ModuleObject.new(
223
- path: code_object.path,
230
+ path: path_to_store(code_object),
224
231
  document: '',
225
232
  tag_list: [],
226
233
  sources: [],
@@ -231,7 +238,7 @@ module Yoda
231
238
  )
232
239
 
233
240
  meta_class_object = Objects::MetaClassObject.new(
234
- path: code_object.path,
241
+ path: path_to_store(code_object),
235
242
  sources: [],
236
243
  primary_source: nil,
237
244
  instance_method_addresses: [],
@@ -244,9 +251,9 @@ module Yoda
244
251
  # @param code_object [::YARD::CodeObjects::Base]
245
252
  # @return [vaid]
246
253
  def register_to_parent_proxy(code_object)
247
- proxy_module = patch.find(code_object.parent.path)
248
- proxy_module.instance_method_addresses.push(code_object.path) if code_object.type == :method
249
- proxy_module.constant_addresses.push(code_object.path) if [:class, :module, :proxy].include?(code_object.type)
254
+ proxy_module = patch.find(path_to_store(code_object.parent))
255
+ proxy_module.instance_method_addresses.push(path_to_store(code_object)) if code_object.type == :method
256
+ proxy_module.constant_addresses.push(path_to_store(code_object)) if [:class, :module, :proxy].include?(code_object.type)
250
257
  end
251
258
 
252
259
  # @param source [(String, Integer)]
@@ -255,6 +262,29 @@ module Yoda
255
262
  file, line = source
256
263
  [root_path ? File.expand_path(file, root_path) : file, line, 0]
257
264
  end
265
+
266
+ # @param code_object [::YARD::CodeObjects::Base]
267
+ # @return [String]
268
+ def path_to_store(object)
269
+ @paths_to_store ||= {}
270
+ @paths_to_store[[object.type, object.path]] ||= calc_path_to_store(object)
271
+ end
272
+
273
+ # @param code_object [::YARD::CodeObjects::Base]
274
+ # @return [String]
275
+ def calc_path_to_store(object)
276
+ return 'Object' if object.root?
277
+ parent_path = path_to_store(object.parent)
278
+
279
+ if object.type == :proxy || object.is_a?(YARD::CodeObjects::Proxy)
280
+ # For now, we suppose the proxy object exists directly under its lexical namespace.
281
+ [path_to_store(object.parent), object.name].join('::')
282
+ elsif object.parent.path == path_to_store(object.parent)
283
+ object.path
284
+ else
285
+ [path_to_store(object.parent), object.name].join(object.sep)
286
+ end.gsub(/^Object::/, '')
287
+ end
258
288
  end
259
289
  end
260
290
  end