Skip to content

Commit

Permalink
Dedupe require path completion
Browse files Browse the repository at this point in the history
  • Loading branch information
tompng committed Dec 5, 2023
1 parent 6d0a9d1 commit c58da2d
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 4 deletions.
13 changes: 9 additions & 4 deletions lib/repl_type_completor/require_paths.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ def require_completions(target_path)
target ||= ''
paths = with_cache [:require_completions, dir] do
gem_and_system_load_paths.flat_map do |load_path|
reject_prefixes = gem_and_system_load_paths.filter_map do |lp|
lp.delete_prefix(load_path).delete_prefix('/') if lp.start_with? load_path
end.reject(&:empty?)
base_dir = File.absolute_path(File.join(load_path, *dir))
with_cache [:requireable_paths, base_dir] do
requireable_paths(base_dir)
requireable_paths(base_dir, reject_prefixes: reject_prefixes)
end
end.sort
end.uniq.sort
end
paths.filter_map do |path|
[*dir, path].join('/') if path.start_with?(target)
Expand Down Expand Up @@ -59,14 +62,16 @@ def gem_and_system_load_paths
end.sort
end

def requireable_paths(base_dir)
def requireable_paths(base_dir, reject_prefixes: [])
ext = ".{rb,#{RbConfig::CONFIG['DLEXT']}}"
ext_regexp = /\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/
files = Dir.glob(["*#{ext}", "*/*#{ext}"], base: base_dir).map { |p| p.sub(ext_regexp, '') }
dirs = Dir.glob('*/*', base: base_dir).filter_map do |path|
"#{path}/" if File.directory?(File.join(base_dir, path))
end
(files + dirs).sort
(files + dirs).sort.reject do |path|
reject_prefixes.any? { path.start_with?(_1) }
end
rescue Errno::EPERM
[]
end
Expand Down
42 changes: 42 additions & 0 deletions test/repl_type_completor/test_require_paths.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require 'repl_type_completor'
require_relative './helper'
require 'tmpdir'

module TestReplTypeCompletor
class RequirePathsTest < TestCase
Expand All @@ -22,5 +23,46 @@ def test_require_relative_paths
assert_not_include ReplTypeCompletor::RequirePaths.require_relative_completions('li', project_root), 'lib/repl_type_completor/version'
assert_include ReplTypeCompletor::RequirePaths.require_relative_completions('lib/', project_root), 'lib/repl_type_completor/version'
end

def clear_cache
ReplTypeCompletor::RequirePaths.instance_eval do
remove_instance_variable(:@gem_and_system_load_paths) if defined? @gem_and_system_load_paths
remove_instance_variable(:@cache) if defined? @cache
end
end

def test_require_paths_no_duplication
# When base_dir/ base_dir/3.3.0 base_dir/3.3.0/arm64-darwin are in $LOAD_PATH,
# "3.3.0/arm64-darwin/file", "arm64-darwin/file" and "file" will all require the same file.
# Completion candidates should only include the shortest one.
load_path_backup = $LOAD_PATH.dup
dir0 = Dir.mktmpdir
dir1 = File.join(dir0, '3.3.0')
dir2 = File.join(dir1, 'arm64-darwin')
dir3 = File.join(dir1, 'test_req_dir')
Dir.mkdir dir1
Dir.mkdir dir2
Dir.mkdir dir3
File.write File.join(dir0, 'test_require_a.rb'), ''
File.write File.join(dir1, 'test_require_a.rb'), ''
File.write File.join(dir2, 'test_require_a.rb'), ''
File.write File.join(dir0, 'test_require_b.rb'), ''
File.write File.join(dir1, 'test_require_c.rb'), ''
File.write File.join(dir2, 'test_require_d.rb'), ''
File.write File.join(dir3, 'test_require_e.rb'), ''
$LOAD_PATH.push(dir0, dir1, dir2)
clear_cache

files = %w[test_req_dir/test_require_e test_require_a test_require_b test_require_c test_require_d]
assert_equal files, ReplTypeCompletor::RequirePaths.require_completions('test_req').sort
candidates = ReplTypeCompletor::RequirePaths.require_completions('')
files.each do |path|
assert_not_include candidates, "3.3.0/#{path}"
assert_not_include candidates, "3.3.0/arm64-darwin/#{path}"
assert_not_include candidates, "arm64-darwin/#{path}"
end
ensure
$LOAD_PATH.replace load_path_backup
end
end
end

0 comments on commit c58da2d

Please sign in to comment.