Skip to content

Commit

Permalink
Index constants (#893)
Browse files Browse the repository at this point in the history
* Support indexing constants

* Extract RubyIndexer::TestCase
  • Loading branch information
st0012 committed Aug 17, 2023
1 parent 02992d1 commit 40fac28
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 38 deletions.
3 changes: 3 additions & 0 deletions lib/ruby_indexer/lib/ruby_indexer/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ class Module < Namespace

class Class < Namespace
end

class Constant < Entry
end
end
end
end
30 changes: 30 additions & 0 deletions lib/ruby_indexer/lib/ruby_indexer/visitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ def visit(node)
add_index_entry(node, Index::Entry::Class)
when YARP::ModuleNode
add_index_entry(node, Index::Entry::Module)
when YARP::ConstantWriteNode, YARP::ConstantOperatorOrWriteNode
add_constant(node)
when YARP::ConstantPathWriteNode, YARP::ConstantPathOperatorOrWriteNode
add_constant_with_path(node)
end
end

Expand All @@ -46,6 +50,32 @@ def visit_all(nodes)

private

sig do
params(
node: T.any(YARP::ConstantWriteNode, YARP::ConstantOperatorOrWriteNode),
).void
end
def add_constant(node)
comments = collect_comments(node)
@index << Index::Entry::Constant.new(fully_qualify_name(node.name), @file_path, node.location, comments)
end

sig do
params(
node: T.any(YARP::ConstantPathWriteNode, YARP::ConstantPathOperatorOrWriteNode),
).void
end
def add_constant_with_path(node)
# ignore variable constants like `var::FOO` or `self.class::FOO`
return unless node.target.parent.nil? || node.target.parent.is_a?(YARP::ConstantReadNode)

name = node.target.location.slice
fully_qualified_name = name.start_with?("::") ? name.delete_prefix("::") : fully_qualify_name(name)

comments = collect_comments(node)
@index << Index::Entry::Constant.new(fully_qualified_name, @file_path, node.location, comments)
end

sig { params(node: T.any(YARP::ClassNode, YARP::ModuleNode), klass: T.class_of(Index::Entry)).void }
def add_index_entry(node, klass)
name = node.constant_path.location.slice
Expand Down
34 changes: 2 additions & 32 deletions lib/ruby_indexer/test/classes_and_modules_test.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
# typed: true
# frozen_string_literal: true

require "test_helper"
require_relative "test_case"

module RubyIndexer
class ClassesAndModulesTest < Minitest::Test
def setup
@index = Index.new
end

class ClassesAndModulesTest < TestCase
def test_empty_statements_class
index(<<~RUBY)
class Foo
Expand Down Expand Up @@ -204,31 +200,5 @@ class Foo; end
second_foo_entry = @index["Foo"][1]
assert_equal("# This is another Foo comment\n", second_foo_entry.comments.join)
end

private

def index(source)
@index.index_single("/fake/path/foo.rb", source)
end

def assert_entry(expected_name, type, expected_location)
entries = @index[expected_name]
refute_empty(entries, "Expected #{expected_name} to be indexed")

entry = entries.first
assert_instance_of(type, entry, "Expected #{expected_name} to be a #{type}")

location = entry.location
location_string =
"#{entry.file_path}:#{location.start_line - 1}-#{location.start_column}" \
":#{location.end_line - 1}-#{location.end_column}"

assert_equal(expected_location, location_string)
end

def refute_entry(expected_name)
entries = @index[expected_name]
assert_nil(entries, "Expected #{expected_name} to not be indexed")
end
end
end
108 changes: 108 additions & 0 deletions lib/ruby_indexer/test/constant_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# typed: true
# frozen_string_literal: true

require_relative "test_case"

module RubyIndexer
class ConstantTest < TestCase
def test_constant_writes
index(<<~RUBY)
FOO = 1
class ::Bar
FOO = 2
end
RUBY

assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-6")
assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-8")
end

def test_constant_or_writes
index(<<~RUBY)
FOO ||= 1
class ::Bar
FOO ||= 2
end
RUBY

assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-8")
assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-10")
end

def test_constant_path_writes
index(<<~RUBY)
class A
FOO = 1
::BAR = 1
module B
FOO = 1
end
end
A::BAZ = 1
RUBY

assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-8")
assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-10")
assert_entry("A::B::FOO", Index::Entry::Constant, "/fake/path/foo.rb:5-4:5-10")
assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:9-0:9-9")
end

def test_constant_path_or_writes
index(<<~RUBY)
class A
FOO ||= 1
::BAR ||= 1
end
A::BAZ ||= 1
RUBY

assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-10")
assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-12")
assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:5-0:5-11")
end

def test_comments_for_constants
index(<<~RUBY)
# FOO comment
FOO = 1
class A
# A::FOO comment
FOO = 1
# ::BAR comment
::BAR = 1
end
# A::BAZ comment
A::BAZ = 1
RUBY

foo_comment = @index["FOO"].first.comments.join("\n")
assert_equal("# FOO comment\n", foo_comment)

a_foo_comment = @index["A::FOO"].first.comments.join("\n")
assert_equal("# A::FOO comment\n", a_foo_comment)

bar_comment = @index["BAR"].first.comments.join("\n")
assert_equal("# ::BAR comment\n", bar_comment)

a_baz_comment = @index["A::BAZ"].first.comments.join("\n")
assert_equal("# A::BAZ comment\n", a_baz_comment)
end

def test_variable_path_constants_are_ignored
index(<<~RUBY)
var::FOO = 1
self.class::FOO = 1
RUBY

assert_no_entry
end
end
end
8 changes: 2 additions & 6 deletions lib/ruby_indexer/test/index_test.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
# typed: true
# frozen_string_literal: true

require "test_helper"
require_relative "test_case"

module RubyIndexer
class IndexTest < Minitest::Test
def setup
@index = Index.new
end

class IndexTest < TestCase
def test_deleting_one_entry_for_a_class
@index.index_single("/fake/path/foo.rb", <<~RUBY)
class Foo
Expand Down
42 changes: 42 additions & 0 deletions lib/ruby_indexer/test/test_case.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# typed: true
# frozen_string_literal: true

require "test_helper"

module RubyIndexer
class TestCase < Minitest::Test
def setup
@index = Index.new
end

private

def index(source)
@index.index_single("/fake/path/foo.rb", source)
end

def assert_entry(expected_name, type, expected_location)
entries = @index[expected_name]
refute_empty(entries, "Expected #{expected_name} to be indexed")

entry = entries.first
assert_instance_of(type, entry, "Expected #{expected_name} to be a #{type}")

location = entry.location
location_string =
"#{entry.file_path}:#{location.start_line - 1}-#{location.start_column}" \
":#{location.end_line - 1}-#{location.end_column}"

assert_equal(expected_location, location_string)
end

def refute_entry(expected_name)
entries = @index[expected_name]
assert_nil(entries, "Expected #{expected_name} to not be indexed")
end

def assert_no_entry
assert_empty(@index.instance_variable_get(:@entries), "Expected nothing to be indexed")
end
end
end

0 comments on commit 40fac28

Please sign in to comment.