Skip to content

Commit

Permalink
Merge branch 'Shopify:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
giovannism20 authored Sep 20, 2023
2 parents 68d8d97 + 59b1999 commit fa10565
Show file tree
Hide file tree
Showing 105 changed files with 6,826 additions and 16,945 deletions.
41 changes: 17 additions & 24 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
PATH
remote: .
specs:
ruby-lsp (0.10.1)
ruby-lsp (0.11.0)
language_server-protocol (~> 3.17.0)
sorbet-runtime
syntax_tree (>= 6.1.1, < 7)
yarp (>= 0.11, < 0.12)
yarp (>= 0.12, < 0.13)

GEM
remote: https://rubygems.org/
Expand All @@ -17,7 +17,6 @@ GEM
debug (1.8.0)
irb (>= 1.5.0)
reline (>= 0.3.1)
diff-lcs (1.5.0)
erubi (1.12.0)
io-console (0.6.0)
irb (1.8.0)
Expand All @@ -44,11 +43,9 @@ GEM
racc (1.7.1)
rainbow (3.1.1)
rake (13.0.6)
rbi (0.0.17)
ast
parser (>= 3.0.0)
rbi (0.1.1)
sorbet-runtime (>= 0.5.9204)
unparser (>= 0.5.6)
yarp (>= 0.11.0)
rdoc (6.5.0)
psych (>= 4.0.0)
regexp_parser (2.8.1)
Expand Down Expand Up @@ -79,42 +76,38 @@ GEM
rubocop (>= 0.90.0)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
sorbet (0.5.11013)
sorbet-static (= 0.5.11013)
sorbet-runtime (0.5.11013)
sorbet-static (0.5.11013-universal-darwin)
sorbet-static (0.5.11013-x86_64-linux)
sorbet-static-and-runtime (0.5.11013)
sorbet (= 0.5.11013)
sorbet-runtime (= 0.5.11013)
spoom (1.2.2)
sorbet (0.5.11026)
sorbet-static (= 0.5.11026)
sorbet-runtime (0.5.11026)
sorbet-static (0.5.11026-universal-darwin)
sorbet-static (0.5.11026-x86_64-linux)
sorbet-static-and-runtime (0.5.11026)
sorbet (= 0.5.11026)
sorbet-runtime (= 0.5.11026)
spoom (1.2.4)
erubi (>= 1.10.0)
sorbet (>= 0.5.10187)
sorbet-runtime (>= 0.5.9204)
sorbet-static-and-runtime (>= 0.5.10187)
syntax_tree (>= 6.1.1)
thor (>= 0.19.2)
stringio (3.0.7)
syntax_tree (6.1.1)
prettier_print (>= 1.2.0)
tapioca (0.11.8)
tapioca (0.11.9)
bundler (>= 2.2.25)
netrc (>= 0.11.0)
parallel (>= 1.21.0)
rbi (~> 0.0.0, >= 0.0.16)
rbi (~> 0.1.0, >= 0.1.0)
sorbet-static-and-runtime (>= 0.5.10187)
spoom (~> 1.2.0, >= 1.2.0)
thor (>= 1.2.0)
yard-sorbet
thor (1.2.2)
unicode-display_width (2.4.2)
unparser (0.6.8)
diff-lcs (~> 1.3)
parser (>= 3.2.0)
yard (0.9.34)
yard-sorbet (0.8.1)
sorbet-runtime (>= 0.5)
yard (>= 0.9)
yarp (0.11.0)
yarp (0.12.0)

PLATFORMS
arm64-darwin
Expand Down
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ end

RDoc::Task.new do |rdoc|
rdoc.main = "README.md"
rdoc.title = "Ruby LSP documentation"
rdoc.rdoc_files.include("*.md", "lib/**/*.rb")
rdoc.rdoc_dir = "docs"
rdoc.markup = "markdown"
Expand Down
14 changes: 8 additions & 6 deletions SERVER_EXTENSIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,13 @@ module RubyLsp

sig do
override.params(
nesting: T::Array[String],
index: RubyIndexer::Index,
emitter: EventEmitter,
message_queue: Thread::Queue,
).returns(T.nilable(Listener[T.nilable(Interface::Hover)]))
end
def create_hover_listener(emitter, message_queue)
def create_hover_listener(nesting, index emitter, message_queue)
# Use the listener factory methods to instantiate listeners with parameters sent by the LSP combined with any
# pre-computed information in the extension. These factory methods are invoked on every request
Hover.new(@config, emitter, message_queue)
Expand Down Expand Up @@ -148,18 +150,18 @@ module RubyLsp
@_response = T.let(nil, ResponseType)
@config = config

# Register that this listener will handle `on_const` events (i.e.: whenever a constant is found in the code)
emitter.register(self, :on_const)
# Register that this listener will handle `on_constant_read` events (i.e.: whenever a constant read is found in the code)
emitter.register(self, :on_constant_read)
end

# Listeners must define methods for each event they registered with the emitter. In this case, we have to define
# `on_const` to specify what this listener should do every time we find a constant
sig { params(node: SyntaxTree::Const).void }
def on_const(node)
sig { params(node: YARP::ConstantReadNode).void }
def on_constant_read(node)
# Certain helpers are made available to listeners to build LSP responses. The classes under `RubyLsp::Interface`
# are generally used to build responses and they match exactly what the specification requests.
contents = RubyLsp::Interface::MarkupContent.new(kind: "markdown", value: "Hello!")
@_response = RubyLsp::Interface::Hover.new(range: range_from_syntax_tree_node(node), contents: contents)
@_response = RubyLsp::Interface::Hover.new(range: range_from_node(node), contents: contents)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.10.1
0.11.0
40 changes: 35 additions & 5 deletions lib/ruby_indexer/lib/ruby_indexer/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ class Configuration

sig { void }
def initialize
excluded_gem_names = Bundler.definition.dependencies.filter_map do |dependency|
dependency.name if dependency.groups == [:development]
end

@excluded_gems = T.let(excluded_gem_names, T::Array[String])
@excluded_gems = T.let(initial_excluded_gems, T::Array[String])
@included_gems = T.let([], T::Array[String])
@excluded_patterns = T.let([File.join("**", "*_test.rb")], T::Array[String])
path = Bundler.settings["path"]
Expand Down Expand Up @@ -174,5 +170,39 @@ def apply_config(config)
@included_patterns.concat(config["included_patterns"]) if config["included_patterns"]
@excluded_magic_comments.concat(config["excluded_magic_comments"]) if config["excluded_magic_comments"]
end

sig { returns(T::Array[String]) }
def initial_excluded_gems
excluded, others = Bundler.definition.dependencies.partition do |dependency|
dependency.groups == [:development]
end

# When working on a gem, we need to make sure that its gemspec dependencies can't be excluded. This is necessary
# because Bundler doesn't assign groups to gemspec dependencies
this_gem = Bundler.definition.dependencies.find { |d| d.to_spec.full_gem_path == Dir.pwd }
others.concat(this_gem.to_spec.dependencies) if this_gem

excluded.each do |dependency|
next unless dependency.runtime?

dependency.to_spec.dependencies.each do |transitive_dependency|
# If the transitive dependency is included in other groups, skip it
next if others.any? { |d| d.name == transitive_dependency.name }

# If the transitive dependency is included as a transitive dependency of a gem outside of the development
# group, skip it
next if others.any? { |d| d.to_spec.dependencies.include?(transitive_dependency) }

excluded << transitive_dependency
end
rescue Gem::MissingSpecError
# If a gem is scoped only to some specific platform, then its dependencies may not be installed either, but they
# are still listed in dependencies. We can't index them because they are not installed for the platform, so we
# just ignore if they're missing
end

excluded.uniq!
excluded.map(&:name)
end
end
end
4 changes: 4 additions & 0 deletions lib/ruby_indexer/lib/ruby_indexer/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,16 @@ class Entry
sig { returns(T::Array[String]) }
attr_reader :comments

sig { returns(Symbol) }
attr_accessor :visibility

sig { params(name: String, file_path: String, location: YARP::Location, comments: T::Array[String]).void }
def initialize(name, file_path, location, comments)
@name = name
@file_path = file_path
@location = location
@comments = comments
@visibility = T.let(:public, Symbol)
end

sig { returns(String) }
Expand Down
28 changes: 28 additions & 0 deletions lib/ruby_indexer/lib/ruby_indexer/visitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ def visit(node)
add_constant(node)
when YARP::ConstantPathWriteNode, YARP::ConstantPathOrWriteNode
add_constant_with_path(node)
when YARP::CallNode
message = node.message
handle_private_constant(node) if message == "private_constant"
end
end

Expand All @@ -50,6 +53,31 @@ def visit_all(nodes)

private

sig { params(node: YARP::CallNode).void }
def handle_private_constant(node)
arguments = node.arguments&.arguments
return unless arguments

first_argument = arguments.first

name = case first_argument
when YARP::StringNode
first_argument.content
when YARP::SymbolNode
first_argument.value
end

return unless name

receiver = node.receiver
name = "#{receiver.slice}::#{name}" if receiver

# The private_constant method does not resolve the constant name. It always points to a constant that needs to
# exist in the current namespace
entries = @index[fully_qualify_name(name)]
entries&.each { |entry| entry.visibility = :private }
end

sig do
params(
node: T.any(YARP::ConstantWriteNode, YARP::ConstantOrWriteNode),
Expand Down
23 changes: 23 additions & 0 deletions lib/ruby_indexer/test/classes_and_modules_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -216,5 +216,28 @@ class Bar; end
second_foo_entry = @index["Bar"][0]
assert_equal("This is a Bar comment", second_foo_entry.comments.join("\n"))
end

def test_private_class_and_module_indexing
index(<<~RUBY)
class A
class B; end
private_constant(:B)
module C; end
private_constant("C")
class D; end
end
RUBY

b_const = @index["A::B"].first
assert_equal(:private, b_const.visibility)

c_const = @index["A::C"].first
assert_equal(:private, c_const.visibility)

d_const = @index["A::D"].first
assert_equal(:public, d_const.visibility)
end
end
end
2 changes: 2 additions & 0 deletions lib/ruby_indexer/test/configuration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ def test_load_configuration_executes_configure_block

assert(indexables.none? { |indexable| indexable.full_path.include?("test/fixtures") })
assert(indexables.none? { |indexable| indexable.full_path.include?("minitest-reporters") })
assert(indexables.none? { |indexable| indexable.full_path.include?("ansi") })
assert(indexables.any? { |indexable| indexable.full_path.include?("sorbet-runtime") })
assert(indexables.none? { |indexable| indexable.full_path == __FILE__ })
end

Expand Down
77 changes: 77 additions & 0 deletions lib/ruby_indexer/test/constant_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,82 @@ def test_variable_path_constants_are_ignored

assert_no_entry
end

def test_private_constant_indexing
index(<<~RUBY)
class A
B = 1
private_constant(:B)
C = 2
private_constant("C")
D = 1
end
RUBY

b_const = @index["A::B"].first
assert_equal(:private, b_const.visibility)

c_const = @index["A::C"].first
assert_equal(:private, c_const.visibility)

d_const = @index["A::D"].first
assert_equal(:public, d_const.visibility)
end

def test_marking_constants_as_private_reopening_namespaces
index(<<~RUBY)
module A
module B
CONST_A = 1
private_constant(:CONST_A)
CONST_B = 2
CONST_C = 3
end
module B
private_constant(:CONST_B)
end
end
module A
module B
private_constant(:CONST_C)
end
end
RUBY

a_const = @index["A::B::CONST_A"].first
assert_equal(:private, a_const.visibility)

b_const = @index["A::B::CONST_B"].first
assert_equal(:private, b_const.visibility)

c_const = @index["A::B::CONST_C"].first
assert_equal(:private, c_const.visibility)
end

def test_marking_constants_as_private_with_receiver
index(<<~RUBY)
module A
module B
CONST_A = 1
CONST_B = 2
end
B.private_constant(:CONST_A)
end
A::B.private_constant(:CONST_B)
RUBY

a_const = @index["A::B::CONST_A"].first
assert_equal(:private, a_const.visibility)

b_const = @index["A::B::CONST_B"].first
assert_equal(:private, b_const.visibility)
end
end
end
Loading

0 comments on commit fa10565

Please sign in to comment.