summaryrefslogtreecommitdiff
path: root/test/irb/type_completion/test_type_completor.rb
blob: 7ae1ada7924df30de7a17339fc36aab6230cbe94 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# frozen_string_literal: true

return unless ENV['WITH_TYPE_COMPLETION_TEST']

# Run test only when Ruby >= 3.0 and %w[prism rbs] are available
return unless RUBY_VERSION >= '3.0.0'
return if RUBY_ENGINE == 'truffleruby' # needs endless method definition
begin
  require 'prism'
  require 'rbs'
rescue LoadError
  return
end

require 'irb/type_completion/completor'
require_relative '../helper'

module TestIRB
  class TypeCompletorTest < TestCase
    def setup
      IRB::TypeCompletion::Types.load_rbs_builder unless IRB::TypeCompletion::Types.rbs_builder
      @completor = IRB::TypeCompletion::Completor.new
    end

    def empty_binding
      binding
    end

    TARGET_REGEXP = /(@@|@|\$)?[a-zA-Z_]*[!?=]?$/

    def assert_completion(code, binding: empty_binding, include: nil, exclude: nil)
      raise ArgumentError if include.nil? && exclude.nil?
      target = code[TARGET_REGEXP]
      candidates = @completor.completion_candidates(code.delete_suffix(target), target, '', bind: binding)
      assert ([*include] - candidates).empty?, "Expected #{candidates} to include #{include}" if include
      assert (candidates & [*exclude]).empty?, "Expected #{candidates} not to include #{exclude}" if exclude
    end

    def assert_doc_namespace(code, namespace, binding: empty_binding)
      target = code[TARGET_REGEXP]
      preposing = code.delete_suffix(target)
      @completor.completion_candidates(preposing, target, '', bind: binding)
      assert_equal namespace, @completor.doc_namespace(preposing, target, '', bind: binding)
    end

    def test_require
      assert_completion("require '", include: 'set')
      assert_completion("require 's", include: 'set')
      Dir.chdir(__dir__ + "/../../..") do
        assert_completion("require_relative 'l", include: 'lib/irb')
      end
      # Incomplete double quote string is InterpolatedStringNode
      assert_completion('require "', include: 'set')
      assert_completion('require "s', include: 'set')
    end

    def test_method_block_sym
      assert_completion('[1].map(&:', include: 'abs')
      assert_completion('[:a].map(&:', exclude: 'abs')
      assert_completion('[1].map(&:a', include: 'abs')
      assert_doc_namespace('[1].map(&:abs', 'Integer#abs')
    end

    def test_symbol
      sym = :test_completion_symbol
      assert_completion(":test_com", include: sym.to_s)
    end

    def test_call
      assert_completion('1.', include: 'abs')
      assert_completion('1.a', include: 'abs')
      assert_completion('ran', include: 'rand')
      assert_doc_namespace('1.abs', 'Integer#abs')
      assert_doc_namespace('Integer.sqrt', 'Integer.sqrt')
      assert_doc_namespace('rand', 'TestIRB::TypeCompletorTest#rand')
      assert_doc_namespace('Object::rand', 'Object.rand')
    end

    def test_lvar
      bind = eval('lvar = 1; binding')
      assert_completion('lva', binding: bind, include: 'lvar')
      assert_completion('lvar.', binding: bind, include: 'abs')
      assert_completion('lvar.a', binding: bind, include: 'abs')
      assert_completion('lvar = ""; lvar.', binding: bind, include: 'ascii_only?')
      assert_completion('lvar = ""; lvar.', include: 'ascii_only?')
      assert_doc_namespace('lvar', 'Integer', binding: bind)
      assert_doc_namespace('lvar.abs', 'Integer#abs', binding: bind)
      assert_doc_namespace('lvar = ""; lvar.ascii_only?', 'String#ascii_only?', binding: bind)
    end

    def test_const
      assert_completion('Ar', include: 'Array')
      assert_completion('::Ar', include: 'Array')
      assert_completion('IRB::V', include: 'VERSION')
      assert_completion('FooBar=1; F', include: 'FooBar')
      assert_completion('::FooBar=1; ::F', include: 'FooBar')
      assert_doc_namespace('Array', 'Array')
      assert_doc_namespace('Array = 1; Array', 'Integer')
      assert_doc_namespace('Object::Array', 'Array')
      assert_completion('::', include: 'Array')
      assert_completion('class ::', include: 'Array')
      assert_completion('module IRB; class T', include: ['TypeCompletion', 'TracePoint'])
    end

    def test_gvar
      assert_completion('$', include: '$stdout')
      assert_completion('$s', include: '$stdout')
      assert_completion('$', exclude: '$foobar')
      assert_completion('$foobar=1; $', include: '$foobar')
      assert_doc_namespace('$foobar=1; $foobar', 'Integer')
      assert_doc_namespace('$stdout', 'IO')
      assert_doc_namespace('$stdout=1; $stdout', 'Integer')
    end

    def test_ivar
      bind = Object.new.instance_eval { @foo = 1; binding }
      assert_completion('@', binding: bind, include: '@foo')
      assert_completion('@f', binding: bind, include: '@foo')
      assert_completion('@bar = 1; @', include: '@bar')
      assert_completion('@bar = 1; @b', include: '@bar')
      assert_doc_namespace('@bar = 1; @bar', 'Integer')
      assert_doc_namespace('@foo', 'Integer', binding: bind)
      assert_doc_namespace('@foo = 1.0; @foo', 'Float', binding: bind)
    end

    def test_cvar
      bind = eval('m=Module.new; module m::M; @@foo = 1; binding; end')
      assert_equal(1, bind.eval('@@foo'))
      assert_completion('@', binding: bind, include: '@@foo')
      assert_completion('@@', binding: bind, include: '@@foo')
      assert_completion('@@f', binding: bind, include: '@@foo')
      assert_doc_namespace('@@foo', 'Integer', binding: bind)
      assert_doc_namespace('@@foo = 1.0; @@foo', 'Float', binding: bind)
      assert_completion('@@bar = 1; @', include: '@@bar')
      assert_completion('@@bar = 1; @@', include: '@@bar')
      assert_completion('@@bar = 1; @@b', include: '@@bar')
      assert_doc_namespace('@@bar = 1; @@bar', 'Integer')
    end

    def test_basic_object
      bo = BasicObject.new
      def bo.foo; end
      bo.instance_eval { @bar = 1 }
      bind = binding
      bo_self_bind = bo.instance_eval { Kernel.binding }
      assert_completion('bo.', binding: bind, include: 'foo')
      assert_completion('def bo.baz; self.', binding: bind, include: 'foo')
      assert_completion('[bo].first.', binding: bind, include: 'foo')
      assert_doc_namespace('bo', 'BasicObject', binding: bind)
      assert_doc_namespace('bo.__id__', 'BasicObject#__id__', binding: bind)
      assert_doc_namespace('v = [bo]; v', 'Array', binding: bind)
      assert_doc_namespace('v = [bo].first; v', 'BasicObject', binding: bind)
      bo_self_bind = bo.instance_eval { Kernel.binding }
      assert_completion('self.', binding: bo_self_bind, include: 'foo')
      assert_completion('@', binding: bo_self_bind, include: '@bar')
      assert_completion('@bar.', binding: bo_self_bind, include: 'abs')
      assert_doc_namespace('self.__id__', 'BasicObject#__id__', binding: bo_self_bind)
      assert_doc_namespace('@bar', 'Integer', binding: bo_self_bind)
      if RUBY_VERSION >= '3.2.0' # Needs Class#attached_object to get instance variables from singleton class
        assert_completion('def bo.baz; @bar.', binding: bind, include: 'abs')
        assert_completion('def bo.baz; @', binding: bind, include: '@bar')
      end
    end

    def test_inspect
      rbs_builder = IRB::TypeCompletion::Types.rbs_builder
      assert_match(/TypeCompletion::Completor\(Prism: \d.+, RBS: \d.+\)/, @completor.inspect)
      IRB::TypeCompletion::Types.instance_variable_set(:@rbs_builder, nil)
      assert_match(/TypeCompletion::Completor\(Prism: \d.+, RBS: loading\)/, @completor.inspect)
      IRB::TypeCompletion::Types.instance_variable_set(:@rbs_load_error, StandardError.new('[err]'))
      assert_match(/TypeCompletion::Completor\(Prism: \d.+, RBS: .+\[err\].+\)/, @completor.inspect)
    ensure
      IRB::TypeCompletion::Types.instance_variable_set(:@rbs_builder, rbs_builder)
      IRB::TypeCompletion::Types.instance_variable_set(:@rbs_load_error, nil)
    end

    def test_none
      candidates = @completor.completion_candidates('(', ')', '', bind: binding)
      assert_equal [], candidates
      assert_doc_namespace('()', nil)
    end
  end
end