File: method_handler_spec.rb

package info (click to toggle)
yard 0.9.38-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,736 kB
  • sloc: ruby: 31,680; javascript: 7,658; makefile: 21
file content (268 lines) | stat: -rw-r--r-- 8,382 bytes parent folder | download
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# frozen_string_literal: true
require File.dirname(__FILE__) + '/spec_helper'

RSpec.describe "YARD::Handlers::Ruby::#{LEGACY_PARSER ? "Legacy::" : ""}MethodHandler" do
  before(:all) do
    log.enter_level(Logger::ERROR) do
      parse_file :method_handler_001, __FILE__
    end
  end

  it "adds methods to parent's #meths list" do
    expect(P(:Foo).meths).to include(P("Foo#method1"))
  end

  it "parses and adds class methods (self.method2)" do
    expect(P(:Foo).meths).to include(P("Foo.method2"))
  end

  it "parses and adds class methods from other namespaces (String.hello)" do
    expect(P("String.hello")).to be_instance_of(CodeObjects::MethodObject)
  end

  [:[], :[]=, :allowed?, :/, :=~, :==, :`, :|, :*, :&, :%, :'^', :-@, :+@, :'~@'].each do |name|
    it "allows valid method #{name}" do
      expect(Registry.at("Foo##{name}")).not_to be nil
    end
  end

  it "allows self.methname" do
    expect(Registry.at("Foo.new")).not_to be nil
  end

  it "marks dynamic methods as such" do
    expect(P('Foo#dynamic').dynamic?).to be true
  end

  it "shows that a method is explicitly defined (if it was originally defined implicitly by attribute)" do
    expect(P('Foo#method1').is_explicit?).to be true
  end

  it "handles parameters" do
    expect(P('Foo#[]').parameters).to eq [['key', "'default'"]]
    expect(P('Foo#/').parameters).to eq [['x', "File.new('x', 'w')"], ['y', '2']]
  end

  it "handles multiline parameters" do
    YARD.parse_string <<-EOF
      class Bar
        def multiline_params(x,
          y, z, zz = 'zz',
          *foo,
          a: 'a', b: 'b',
          c: 'c',
          **bar,
          &blk
        )
        end
      end
    EOF

    sig = "def multiline_params(x, y, z, zz = 'zz', *foo, a: 'a', b: 'b', c: 'c', **bar, &blk)"
    expect(P('Bar#multiline_params').signature).to eq sig
  end if YARD.ruby2?

  it "handles endless method definitions without parameters" do
    YARD.parse_string <<-EOF
      class Bar
        def endless = true
      end
    EOF

    expect(P('Bar#endless').signature).to eq "def endless"
  end if YARD.ruby3?

  it "handles method with arguments forwarding" do
    YARD.parse_string <<-EOF
      class Bar
        def method_with_forwarding(...)
          forward_to(...)
        end
      end
    EOF

    expect(P('Bar#method_with_forwarding').signature).to eq "def method_with_forwarding(...)"
  end if YARD.ruby3? # this is 2.7+ but we can just test on 3+

  it "handles method with anonymous block" do
    YARD.parse_string <<-EOF
      class Bar
        def anon_block_method(&)
          baz(a, &)
        end
      end
    EOF

    expect(P('Bar#anon_block_method').signature).to eq "def anon_block_method(&)"
  end if YARD.ruby31?

  it "handles endless method definitions with parameters" do
    YARD.parse_string <<-EOF
      class Bar
        def endless_with_arg(arg = true) = true
      end
    EOF

    expect(P('Bar#endless_with_arg').signature).to eq "def endless_with_arg(arg = true)"
  end if YARD.ruby3?

  it "handles method signature with no parameters" do
    YARD.parse_string "class Bar; def foo; end end"
    expect(P('Bar#foo').signature).to eq 'def foo'
  end

  it "handles opts = {} as parameter" do
    expect(P('Foo#optsmeth').parameters).to eq [['x', nil], ['opts', '{}']]
  end

  it "handles &block as parameter" do
    expect(P('Foo#blockmeth').parameters).to eq [['x', nil], ['&block', nil]]
  end

  it "handles double splats" do
    expect(P('Foo#d_splat').parameters).to eq [["reg", nil], ["**opts", nil]]
    expect(P('Foo#d_unnamed_splat').parameters).to eq []
  end

  it "handles **nil with named block" do
    YARD.parse_string <<-RUBY
      class KwNilBlock
        def no_kwargs_with_block(**nil, &block); end
      end
    RUBY

    expect(P('KwNilBlock#no_kwargs_with_block').parameters).to eq [['&block', nil]]
  end if YARD.ruby3?

  it "handles overloads" do
    meth = P('Foo#foo')

    o1 = meth.tags(:overload).first
    expect(o1.name).to eq :bar
    expect(o1.parameters).to eq [['a', nil], ['b', "1"]]
    expect(o1.tag(:return).type).to eq "String"

    o2 = meth.tags(:overload)[1]
    expect(o2.name).to eq :baz
    expect(o2.parameters).to eq [['b', nil], ['c', nil]]
    expect(o2.tag(:return).type).to eq "Fixnum"

    o3 = meth.tags(:overload)[2]
    expect(o3.name).to eq :bang
    expect(o3.parameters).to eq [['d', nil], ['e', nil]]
    expect(o3.docstring).to be_empty
    expect(o3.docstring).to be_blank
  end

  it "sets a return tag if not set on #initialize" do
    meth = P('Foo#initialize')

    expect(meth).to have_tag(:return)
    expect(meth.tag(:return).types).to eq ["Foo"]
    expect(meth.tag(:return).text).to eq "a new instance of Foo"
  end

  %w(inherited included method_added method_removed method_undefined).each do |meth|
    it "sets @private tag on #{meth} callback method if no docstring is set" do
      expect(P('Foo.' + meth)).to have_tag(:private)
    end
  end

  it "does not set @private tag on extended callback method since docstring is set" do
    expect(P('Foo.extended')).not_to have_tag(:private)
  end

  it "adds @return [Boolean] tag to methods ending in ? without return types" do
    meth = P('Foo#boolean?')
    expect(meth).to have_tag(:return)
    expect(meth.tag(:return).types).to eq ['Boolean']
  end

  it "adds Boolean type to return tag without types" do
    meth = P('Foo#boolean2?')
    expect(meth).to have_tag(:return)
    expect(meth.tag(:return).types).to eq ['Boolean']
  end

  it "does not change return type for method ending in ? with return types set" do
    meth = P('Foo#boolean3?')
    expect(meth).to have_tag(:return)
    expect(meth.tag(:return).types).to eq ['NotBoolean', 'nil']
  end

  it "does not change return type for method ending in ? with return types set by @overload" do
    meth = P('Foo#rainy?')
    expect(meth).to have_tag(:overload)
    expect(meth.tag(:overload)).to have_tag(:return)
    expect(meth).not_to have_tag(:return)
  end

  it "adds method writer to existing attribute" do
    expect(Registry.at('Foo#attr_name')).to be_reader
    expect(Registry.at('Foo#attr_name=')).to be_writer
  end

  it "adds method reader to existing attribute" do
    expect(Registry.at('Foo#attr_name2')).to be_reader
    expect(Registry.at('Foo#attr_name2=')).to be_writer
  end

  it "generates an options parameter if @option refers to an undocumented parameter" do
    meth = P('Foo#auto_opts')
    expect(meth).to have_tag(:param)
    expect(meth.tag(:param).name).to eq "opts"
    expect(meth.tag(:param).types).to eq ["Hash"]
  end

  it "raises an undocumentable error when a method is defined on an object instance" do
    undoc_error "error = Foo; def error.at(foo) end"
    expect(Registry.at('error')).to be nil
  end

  it "allows class method to be defined on constant reference object" do
    expect(Registry.at('Foo.meth_on_const')).not_to be nil
    expect(Registry.at('Foo.meth2_on_const')).not_to be nil
  end

  it "copies alias information on method (re-)definition to new method" do
    expect(Registry.at('D').aliases).to be_empty
    expect(Registry.at('D#b').is_alias?).to be false
    expect(Registry.at('D#a').is_alias?).to be false
  end

  it "adds macros for class methods" do
    macro = CodeObjects::MacroObject.find('prop')
    expect(macro).not_to be nil
    expect(macro.macro_data).to eq "@!method $1(value)\n$3\n@return [$2]"
    expect(macro.method_object).to eq Registry.at('E.property')
    expect(macro).to be_attached
    obj = Registry.at('E#foo')
    expect(obj).not_to be nil
    expect(obj.docstring).to eq 'create a foo'
    expect(obj.signature).to eq 'def foo(value)'
    expect(obj.tag(:return).types).to eq ['String']
  end

  it "handles macros on any object" do
    macro = CodeObjects::MacroObject.find('xyz')
    expect(macro).not_to be nil
    expect(macro.macro_data).to eq '@!method $1'
  end

  it "skips macros on instance methods" do
    expect(Registry.at('E#a')).to be nil
  end

  it "warns if the macro name is invalid" do
    expect(log).to receive(:warn).with(/Invalid directive.*@!macro/)
    YARD.parse_string "class Foo\n# @!macro\ndef self.foo; end\nend"
  end

  it "handles 'def end' methods" do
    obj = Registry.at('F::A#foo')
    expect(obj).not_to be nil
    obj = Registry.at('F::A#bar')
    expect(obj).not_to be nil
    expect(obj.docstring).to eq 'PASS'
  end
end