ruby-lsp 0.7.6 → 0.8.1
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 +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp +41 -33
- data/exe/ruby-lsp-check +2 -2
- data/lib/core_ext/uri.rb +40 -0
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +91 -0
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +122 -0
- data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +121 -0
- data/lib/ruby_indexer/ruby_indexer.rb +19 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +204 -0
- data/lib/ruby_indexer/test/configuration_test.rb +35 -0
- data/lib/ruby_indexer/test/constant_test.rb +108 -0
- data/lib/ruby_indexer/test/index_test.rb +94 -0
- data/lib/ruby_indexer/test/test_case.rb +42 -0
- data/lib/ruby_lsp/document.rb +3 -3
- data/lib/ruby_lsp/executor.rb +131 -24
- data/lib/ruby_lsp/extension.rb +24 -0
- data/lib/ruby_lsp/internal.rb +4 -0
- data/lib/ruby_lsp/listener.rb +15 -14
- data/lib/ruby_lsp/requests/code_actions.rb +3 -3
- data/lib/ruby_lsp/requests/code_lens.rb +10 -24
- data/lib/ruby_lsp/requests/definition.rb +55 -8
- data/lib/ruby_lsp/requests/diagnostics.rb +3 -2
- data/lib/ruby_lsp/requests/document_link.rb +4 -3
- data/lib/ruby_lsp/requests/formatting.rb +3 -2
- data/lib/ruby_lsp/requests/hover.rb +4 -18
- data/lib/ruby_lsp/requests/on_type_formatting.rb +4 -6
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +5 -0
- data/lib/ruby_lsp/requests/support/formatter_runner.rb +1 -1
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +2 -2
- data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +2 -3
- data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +2 -3
- data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +3 -2
- data/lib/ruby_lsp/server.rb +10 -2
- data/lib/ruby_lsp/setup_bundler.rb +28 -14
- data/lib/ruby_lsp/store.rb +20 -13
- data/lib/ruby_lsp/utils.rb +1 -1
- metadata +27 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b43407d4d6309c7243a6c69aca8166f6ab3548bd47e939c016efe26c1d8a4a76
|
4
|
+
data.tar.gz: cc5f638aab3322054cc2a09b60fe22a6e5ee8196d88492b4139907c2495c7d5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dda98d73d8ba39cdfaffa3362177ef96887eccff10332012d43f79bdc78643d3bb79a9e43e05a5c21e4550cf9b86d8d00b5742fb51b124d2ed8c8112bc6f1d1a
|
7
|
+
data.tar.gz: 25a169a3eb717f5f11b5b911c91e11e578d33734f6f616c0c92fd6ebcc89d69ddda6abb13410ef231acc398ba0472e17c778d245b89f1285d64317df98c2299c
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.8.1
|
data/exe/ruby-lsp
CHANGED
@@ -1,6 +1,46 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require "optparse"
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
parser = OptionParser.new do |opts|
|
8
|
+
opts.banner = "Usage: ruby-lsp [options]"
|
9
|
+
|
10
|
+
opts.on("--version", "Print ruby-lsp version") do
|
11
|
+
require "ruby-lsp"
|
12
|
+
puts RubyLsp::VERSION
|
13
|
+
exit(0)
|
14
|
+
end
|
15
|
+
|
16
|
+
opts.on("--debug", "Launch the Ruby LSP with a debugger attached") do
|
17
|
+
options[:debug] = true
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on(
|
21
|
+
"--branch [BRANCH]",
|
22
|
+
"Launch the Ruby LSP using the specified branch rather than the release version",
|
23
|
+
) do |branch|
|
24
|
+
options[:branch] = branch
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("-h", "--help", "Print this help") do
|
28
|
+
puts opts.help
|
29
|
+
puts
|
30
|
+
puts "See https://shopify.github.io/ruby-lsp/ for more information"
|
31
|
+
exit(0)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
begin
|
36
|
+
parser.parse!
|
37
|
+
rescue OptionParser::InvalidOption => e
|
38
|
+
warn(e)
|
39
|
+
warn("")
|
40
|
+
warn(parser.help)
|
41
|
+
exit(1)
|
42
|
+
end
|
43
|
+
|
4
44
|
# When we're running without bundler, then we need to make sure the custom bundle is fully configured and re-execute
|
5
45
|
# using `BUNDLE_GEMFILE=.ruby-lsp/Gemfile bundle exec ruby-lsp` so that we have access to the gems that are a part of
|
6
46
|
# the application's bundle
|
@@ -8,7 +48,7 @@ if ENV["BUNDLE_GEMFILE"].nil?
|
|
8
48
|
require_relative "../lib/ruby_lsp/setup_bundler"
|
9
49
|
|
10
50
|
begin
|
11
|
-
bundle_gemfile, bundle_path = RubyLsp::SetupBundler.new(Dir.pwd).setup!
|
51
|
+
bundle_gemfile, bundle_path = RubyLsp::SetupBundler.new(Dir.pwd, branch: options[:branch]).setup!
|
12
52
|
rescue RubyLsp::SetupBundler::BundleNotLocked
|
13
53
|
warn("Project contains a Gemfile, but no Gemfile.lock. Run `bundle install` to lock gems and restart the server")
|
14
54
|
exit(78)
|
@@ -38,38 +78,6 @@ end
|
|
38
78
|
|
39
79
|
require_relative "../lib/ruby_lsp/internal"
|
40
80
|
|
41
|
-
require "optparse"
|
42
|
-
|
43
|
-
options = {}
|
44
|
-
parser = OptionParser.new do |opts|
|
45
|
-
opts.banner = "Usage: ruby-lsp [options]"
|
46
|
-
|
47
|
-
opts.on("--version", "Print ruby-lsp version") do
|
48
|
-
puts RubyLsp::VERSION
|
49
|
-
exit(0)
|
50
|
-
end
|
51
|
-
|
52
|
-
opts.on("--debug", "Launch the Ruby LSP with a debugger attached") do
|
53
|
-
options[:debug] = true
|
54
|
-
end
|
55
|
-
|
56
|
-
opts.on("-h", "--help", "Print this help") do
|
57
|
-
puts opts.help
|
58
|
-
puts
|
59
|
-
puts "See https://shopify.github.io/ruby-lsp/ for more information"
|
60
|
-
exit(0)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
begin
|
65
|
-
parser.parse!
|
66
|
-
rescue OptionParser::InvalidOption => e
|
67
|
-
warn(e)
|
68
|
-
warn("")
|
69
|
-
warn(parser.help)
|
70
|
-
exit(1)
|
71
|
-
end
|
72
|
-
|
73
81
|
if options[:debug]
|
74
82
|
if ["x64-mingw-ucrt", "x64-mingw32"].include?(RUBY_PLATFORM)
|
75
83
|
puts "Debugging is not supported on Windows"
|
data/exe/ruby-lsp-check
CHANGED
@@ -30,13 +30,13 @@ message_queue = Thread::Queue.new
|
|
30
30
|
executor = RubyLsp::Executor.new(store, message_queue)
|
31
31
|
|
32
32
|
files.each_with_index do |file, index|
|
33
|
-
uri = "file://#{file}"
|
33
|
+
uri = URI("file://#{file}")
|
34
34
|
store.set(uri: uri, source: File.read(file), version: 1)
|
35
35
|
|
36
36
|
# Executing any of the automatic requests will execute all of them, so here we just pick one
|
37
37
|
result = executor.execute({
|
38
38
|
method: "textDocument/documentSymbol",
|
39
|
-
params: { textDocument: { uri: uri } },
|
39
|
+
params: { textDocument: { uri: uri.to_s } },
|
40
40
|
})
|
41
41
|
|
42
42
|
error = result.error
|
data/lib/core_ext/uri.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module URI
|
5
|
+
class Generic
|
6
|
+
class << self
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { params(path: String, scheme: String).returns(URI::Generic) }
|
10
|
+
def from_path(path:, scheme: "file")
|
11
|
+
# On Windows, if the path begins with the disk name, we need to add a leading slash to make it a valid URI
|
12
|
+
escaped_path = if /^[A-Z]:/i.match?(path)
|
13
|
+
DEFAULT_PARSER.escape("/#{path}")
|
14
|
+
else
|
15
|
+
DEFAULT_PARSER.escape(path)
|
16
|
+
end
|
17
|
+
|
18
|
+
build(scheme: scheme, path: escaped_path)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
extend T::Sig
|
23
|
+
|
24
|
+
sig { returns(T.nilable(String)) }
|
25
|
+
def to_standardized_path
|
26
|
+
parsed_path = path
|
27
|
+
return unless parsed_path
|
28
|
+
|
29
|
+
unescaped_path = CGI.unescape(parsed_path)
|
30
|
+
|
31
|
+
# On Windows, when we're getting the file system path back from the URI, we need to remove the leading forward
|
32
|
+
# slash
|
33
|
+
if %r{^/[A-Z]:}i.match?(unescaped_path)
|
34
|
+
unescaped_path.delete_prefix("/")
|
35
|
+
else
|
36
|
+
unescaped_path
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyIndexer
|
5
|
+
class Configuration
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
CONFIGURATION_SCHEMA = T.let(
|
9
|
+
{
|
10
|
+
"excluded_gems" => Array,
|
11
|
+
"included_gems" => Array,
|
12
|
+
"excluded_patterns" => Array,
|
13
|
+
"included_patterns" => Array,
|
14
|
+
}.freeze,
|
15
|
+
T::Hash[String, T::Class[Object]],
|
16
|
+
)
|
17
|
+
|
18
|
+
sig { void }
|
19
|
+
def initialize
|
20
|
+
development_only_dependencies = Bundler.definition.dependencies.filter_map do |dependency|
|
21
|
+
dependency.name if dependency.groups == [:development]
|
22
|
+
end
|
23
|
+
|
24
|
+
@excluded_gems = T.let(development_only_dependencies, T::Array[String])
|
25
|
+
@included_gems = T.let([], T::Array[String])
|
26
|
+
@excluded_patterns = T.let(["*_test.rb"], T::Array[String])
|
27
|
+
@included_patterns = T.let(["#{Dir.pwd}/**/*.rb"], T::Array[String])
|
28
|
+
end
|
29
|
+
|
30
|
+
sig { void }
|
31
|
+
def load_config
|
32
|
+
return unless File.exist?(".index.yml")
|
33
|
+
|
34
|
+
config = YAML.parse_file(".index.yml")
|
35
|
+
return unless config
|
36
|
+
|
37
|
+
config_hash = config.to_ruby
|
38
|
+
validate_config!(config_hash)
|
39
|
+
apply_config(config_hash)
|
40
|
+
rescue Psych::SyntaxError => e
|
41
|
+
raise e, "Syntax error while loading .index.yml configuration: #{e.message}"
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { returns(T::Array[String]) }
|
45
|
+
def files_to_index
|
46
|
+
files_to_index = $LOAD_PATH.flat_map { |p| Dir.glob("#{p}/**/*.rb", base: p) }
|
47
|
+
|
48
|
+
@included_patterns.each do |pattern|
|
49
|
+
files_to_index.concat(Dir.glob(pattern, File::FNM_PATHNAME | File::FNM_EXTGLOB))
|
50
|
+
end
|
51
|
+
|
52
|
+
excluded_gem_paths = (@excluded_gems - @included_gems).filter_map do |gem_name|
|
53
|
+
Gem::Specification.find_by_name(gem_name).full_gem_path
|
54
|
+
rescue Gem::MissingSpecError
|
55
|
+
warn("Gem #{gem_name} is excluded in .index.yml, but that gem was not found in the bundle")
|
56
|
+
end
|
57
|
+
|
58
|
+
files_to_index.reject! do |path|
|
59
|
+
@excluded_patterns.any? { |pattern| File.fnmatch?(pattern, path, File::FNM_PATHNAME | File::FNM_EXTGLOB) } ||
|
60
|
+
excluded_gem_paths.any? { |gem_path| File.fnmatch?("#{gem_path}/**/*.rb", path) }
|
61
|
+
end
|
62
|
+
files_to_index.uniq!
|
63
|
+
files_to_index
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
sig { params(config: T::Hash[String, T.untyped]).void }
|
69
|
+
def validate_config!(config)
|
70
|
+
errors = config.filter_map do |key, value|
|
71
|
+
type = CONFIGURATION_SCHEMA[key]
|
72
|
+
|
73
|
+
if type.nil?
|
74
|
+
"Unknown configuration option: #{key}"
|
75
|
+
elsif !value.is_a?(type)
|
76
|
+
"Expected #{key} to be a #{type}, but got #{value.class}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
raise ArgumentError, errors.join("\n") if errors.any?
|
81
|
+
end
|
82
|
+
|
83
|
+
sig { params(config: T::Hash[String, T.untyped]).void }
|
84
|
+
def apply_config(config)
|
85
|
+
@excluded_gems.concat(config["excluded_gems"]) if config["excluded_gems"]
|
86
|
+
@included_gems.concat(config["included_gems"]) if config["included_gems"]
|
87
|
+
@excluded_patterns.concat(config["excluded_patterns"]) if config["excluded_patterns"]
|
88
|
+
@included_patterns.concat(config["included_patterns"]) if config["included_patterns"]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyIndexer
|
5
|
+
class Index
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { void }
|
9
|
+
def initialize
|
10
|
+
# Holds all entries in the index using the following format:
|
11
|
+
# {
|
12
|
+
# "Foo" => [#<Entry::Class>, #<Entry::Class>],
|
13
|
+
# "Foo::Bar" => [#<Entry::Class>],
|
14
|
+
# }
|
15
|
+
@entries = T.let({}, T::Hash[String, T::Array[Entry]])
|
16
|
+
|
17
|
+
# Holds references to where entries where discovered so that we can easily delete them
|
18
|
+
# {
|
19
|
+
# "/my/project/foo.rb" => [#<Entry::Class>, #<Entry::Class>],
|
20
|
+
# "/my/project/bar.rb" => [#<Entry::Class>],
|
21
|
+
# }
|
22
|
+
@files_to_entries = T.let({}, T::Hash[String, T::Array[Entry]])
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { params(path: String).void }
|
26
|
+
def delete(path)
|
27
|
+
# For each constant discovered in `path`, delete the associated entry from the index. If there are no entries
|
28
|
+
# left, delete the constant from the index.
|
29
|
+
@files_to_entries[path]&.each do |entry|
|
30
|
+
entries = @entries[entry.name]
|
31
|
+
next unless entries
|
32
|
+
|
33
|
+
# Delete the specific entry from the list for this name
|
34
|
+
entries.delete(entry)
|
35
|
+
# If all entries were deleted, then remove the name from the hash
|
36
|
+
@entries.delete(entry.name) if entries.empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
@files_to_entries.delete(path)
|
40
|
+
end
|
41
|
+
|
42
|
+
sig { params(entry: Entry).void }
|
43
|
+
def <<(entry)
|
44
|
+
(@entries[entry.name] ||= []) << entry
|
45
|
+
(@files_to_entries[entry.file_path] ||= []) << entry
|
46
|
+
end
|
47
|
+
|
48
|
+
sig { params(fully_qualified_name: String).returns(T.nilable(T::Array[Entry])) }
|
49
|
+
def [](fully_qualified_name)
|
50
|
+
@entries[fully_qualified_name.delete_prefix("::")]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Try to find the entry based on the nesting from the most specific to the least specific. For example, if we have
|
54
|
+
# the nesting as ["Foo", "Bar"] and the name as "Baz", we will try to find it in this order:
|
55
|
+
# 1. Foo::Bar::Baz
|
56
|
+
# 2. Foo::Baz
|
57
|
+
# 3. Baz
|
58
|
+
sig { params(name: String, nesting: T::Array[String]).returns(T.nilable(T::Array[Entry])) }
|
59
|
+
def resolve(name, nesting)
|
60
|
+
(nesting.length + 1).downto(0).each do |i|
|
61
|
+
prefix = T.must(nesting[0...i]).join("::")
|
62
|
+
full_name = prefix.empty? ? name : "#{prefix}::#{name}"
|
63
|
+
entries = @entries[full_name]
|
64
|
+
return entries if entries
|
65
|
+
end
|
66
|
+
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
sig { params(paths: T::Array[String]).void }
|
71
|
+
def index_all(paths: RubyIndexer.configuration.files_to_index)
|
72
|
+
paths.each { |path| index_single(path) }
|
73
|
+
end
|
74
|
+
|
75
|
+
sig { params(path: String, source: T.nilable(String)).void }
|
76
|
+
def index_single(path, source = nil)
|
77
|
+
content = source || File.read(path)
|
78
|
+
visitor = IndexVisitor.new(self, YARP.parse(content), path)
|
79
|
+
visitor.run
|
80
|
+
end
|
81
|
+
|
82
|
+
class Entry
|
83
|
+
extend T::Sig
|
84
|
+
|
85
|
+
sig { returns(String) }
|
86
|
+
attr_reader :name
|
87
|
+
|
88
|
+
sig { returns(String) }
|
89
|
+
attr_reader :file_path
|
90
|
+
|
91
|
+
sig { returns(YARP::Location) }
|
92
|
+
attr_reader :location
|
93
|
+
|
94
|
+
sig { returns(T::Array[String]) }
|
95
|
+
attr_reader :comments
|
96
|
+
|
97
|
+
sig { params(name: String, file_path: String, location: YARP::Location, comments: T::Array[String]).void }
|
98
|
+
def initialize(name, file_path, location, comments)
|
99
|
+
@name = name
|
100
|
+
@file_path = file_path
|
101
|
+
@location = location
|
102
|
+
@comments = comments
|
103
|
+
end
|
104
|
+
|
105
|
+
class Namespace < Entry
|
106
|
+
sig { returns(String) }
|
107
|
+
def short_name
|
108
|
+
T.must(@name.split("::").last)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class Module < Namespace
|
113
|
+
end
|
114
|
+
|
115
|
+
class Class < Namespace
|
116
|
+
end
|
117
|
+
|
118
|
+
class Constant < Entry
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyIndexer
|
5
|
+
class IndexVisitor < YARP::Visitor
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { params(index: Index, parse_result: YARP::ParseResult, file_path: String).void }
|
9
|
+
def initialize(index, parse_result, file_path)
|
10
|
+
@index = index
|
11
|
+
@parse_result = parse_result
|
12
|
+
@file_path = file_path
|
13
|
+
@stack = T.let([], T::Array[String])
|
14
|
+
@comments_by_line = T.let(
|
15
|
+
parse_result.comments.to_h do |c|
|
16
|
+
[c.location.start_line, c]
|
17
|
+
end,
|
18
|
+
T::Hash[Integer, YARP::Comment],
|
19
|
+
)
|
20
|
+
|
21
|
+
super()
|
22
|
+
end
|
23
|
+
|
24
|
+
sig { void }
|
25
|
+
def run
|
26
|
+
visit(@parse_result.value)
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { params(node: T.nilable(YARP::Node)).void }
|
30
|
+
def visit(node)
|
31
|
+
case node
|
32
|
+
when YARP::ProgramNode, YARP::StatementsNode
|
33
|
+
visit_child_nodes(node)
|
34
|
+
when YARP::ClassNode
|
35
|
+
add_index_entry(node, Index::Entry::Class)
|
36
|
+
when YARP::ModuleNode
|
37
|
+
add_index_entry(node, Index::Entry::Module)
|
38
|
+
when YARP::ConstantWriteNode, YARP::ConstantOperatorOrWriteNode
|
39
|
+
add_constant(node)
|
40
|
+
when YARP::ConstantPathWriteNode, YARP::ConstantPathOperatorOrWriteNode
|
41
|
+
add_constant_with_path(node)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Override to avoid using `map` instead of `each`
|
46
|
+
sig { params(nodes: T::Array[T.nilable(YARP::Node)]).void }
|
47
|
+
def visit_all(nodes)
|
48
|
+
nodes.each { |node| visit(node) }
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
sig do
|
54
|
+
params(
|
55
|
+
node: T.any(YARP::ConstantWriteNode, YARP::ConstantOperatorOrWriteNode),
|
56
|
+
).void
|
57
|
+
end
|
58
|
+
def add_constant(node)
|
59
|
+
comments = collect_comments(node)
|
60
|
+
@index << Index::Entry::Constant.new(fully_qualify_name(node.name), @file_path, node.location, comments)
|
61
|
+
end
|
62
|
+
|
63
|
+
sig do
|
64
|
+
params(
|
65
|
+
node: T.any(YARP::ConstantPathWriteNode, YARP::ConstantPathOperatorOrWriteNode),
|
66
|
+
).void
|
67
|
+
end
|
68
|
+
def add_constant_with_path(node)
|
69
|
+
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
70
|
+
return unless node.target.parent.nil? || node.target.parent.is_a?(YARP::ConstantReadNode)
|
71
|
+
|
72
|
+
name = node.target.location.slice
|
73
|
+
fully_qualified_name = name.start_with?("::") ? name.delete_prefix("::") : fully_qualify_name(name)
|
74
|
+
|
75
|
+
comments = collect_comments(node)
|
76
|
+
@index << Index::Entry::Constant.new(fully_qualified_name, @file_path, node.location, comments)
|
77
|
+
end
|
78
|
+
|
79
|
+
sig { params(node: T.any(YARP::ClassNode, YARP::ModuleNode), klass: T.class_of(Index::Entry)).void }
|
80
|
+
def add_index_entry(node, klass)
|
81
|
+
name = node.constant_path.location.slice
|
82
|
+
|
83
|
+
unless /^[A-Z:]/.match?(name)
|
84
|
+
return visit_child_nodes(node)
|
85
|
+
end
|
86
|
+
|
87
|
+
fully_qualified_name = name.start_with?("::") ? name : fully_qualify_name(name)
|
88
|
+
name.delete_prefix!("::")
|
89
|
+
|
90
|
+
comments = collect_comments(node)
|
91
|
+
@index << klass.new(fully_qualified_name, @file_path, node.location, comments)
|
92
|
+
@stack << name
|
93
|
+
visit_child_nodes(node)
|
94
|
+
@stack.pop
|
95
|
+
end
|
96
|
+
|
97
|
+
sig { params(node: YARP::Node).returns(T::Array[String]) }
|
98
|
+
def collect_comments(node)
|
99
|
+
comments = []
|
100
|
+
|
101
|
+
start_line = node.location.start_line - 1
|
102
|
+
start_line -= 1 unless @comments_by_line.key?(start_line)
|
103
|
+
|
104
|
+
start_line.downto(1) do |line|
|
105
|
+
comment = @comments_by_line[line]
|
106
|
+
break unless comment
|
107
|
+
|
108
|
+
comments.unshift(comment.location.slice)
|
109
|
+
end
|
110
|
+
|
111
|
+
comments
|
112
|
+
end
|
113
|
+
|
114
|
+
sig { params(name: String).returns(String) }
|
115
|
+
def fully_qualify_name(name)
|
116
|
+
return name if @stack.empty?
|
117
|
+
|
118
|
+
"#{@stack.join("::")}::#{name}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "yaml"
|
5
|
+
|
6
|
+
require "ruby_indexer/lib/ruby_indexer/visitor"
|
7
|
+
require "ruby_indexer/lib/ruby_indexer/index"
|
8
|
+
require "ruby_indexer/lib/ruby_indexer/configuration"
|
9
|
+
|
10
|
+
module RubyIndexer
|
11
|
+
class << self
|
12
|
+
extend T::Sig
|
13
|
+
|
14
|
+
sig { returns(Configuration) }
|
15
|
+
def configuration
|
16
|
+
@configuration ||= T.let(Configuration.new, T.nilable(Configuration))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|