File: yardoc_spec.rb

package info (click to toggle)
yard 0.9.37-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,720 kB
  • sloc: ruby: 31,354; javascript: 7,608; makefile: 21
file content (920 lines) | stat: -rw-r--r-- 34,584 bytes parent folder | download | duplicates (2)
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
# frozen_string_literal: true

RSpec.describe YARD::CLI::Yardoc do
  before do
    @yardoc = YARD::CLI::Yardoc.new
    @yardoc.statistics = false
    @yardoc.use_document_file = false
    @yardoc.use_yardopts_file = false
    @yardoc.generate = false
    allow(Templates::Engine).to receive(:render)
    allow(Templates::Engine).to receive(:generate)
    allow(YARD).to receive(:parse)
    allow(Registry).to receive(:load)
    allow(Registry).to receive(:save)
    allow(File).to receive(:open!)
  end

  describe "Defaults" do
    before do
      @yardoc = CLI::Yardoc.new
      allow(@yardoc).to receive(:yardopts).and_return([])
      allow(@yardoc).to receive(:support_rdoc_document_file!).and_return([])
      @yardoc.parse_arguments
    end

    it "does not use cache by default" do
      expect(@yardoc.use_cache).to be false
    end

    it "prints statistics by default" do
      expect(@yardoc.statistics).to be true
    end

    it "generates output by default" do
      expect(@yardoc.generate).to be true
    end

    it "reads .yardopts by default" do
      expect(@yardoc.use_yardopts_file).to be true
    end

    it "reads .document by default" do
      expect(@yardoc.use_document_file).to be true
    end

    it "uses lib, app, and ext as default file glob paths" do
      expect(@yardoc.files).to eq Parser::SourceParser::DEFAULT_PATH_GLOB
    end

    it "uses rdoc as default markup type (but falls back on none)" do
      expect(@yardoc.options.markup).to eq :rdoc
    end

    it "uses default as default template" do
      expect(@yardoc.options.template).to eq :default
    end

    it "uses HTML as default format" do
      expect(@yardoc.options.format).to eq :html
    end

    it "uses 'Object' as default return type" do
      expect(@yardoc.options.default_return).to eq 'Object'
    end

    it "does not hide void return types by default" do
      expect(@yardoc.options.hide_void_return).to be false
    end

    it "only shows public visibility by default" do
      expect(@yardoc.visibilities).to eq [:public]
    end

    it "does not list objects by default" do
      expect(@yardoc.list).to be false
    end

    it "does not embed mixins by default" do
      expect(@yardoc.options.embed_mixins).to be_empty
    end

    it "does not set any locale by default" do
      expect(@yardoc.options.locale).to be nil
    end
  end

  describe "General options" do
    def self.should_accept(*args, &block)
      @counter ||= 0
      @counter += 1
      counter = @counter
      define_method("test_options_#{@counter}", &block)
      args.each do |arg|
        it("accepts #{arg}") { send("test_options_#{counter}", arg) }
      end
    end

    should_accept('--single-db') do |arg|
      @yardoc.parse_arguments(arg)
      expect(Registry.single_object_db).to be true
      Registry.single_object_db = nil
    end

    should_accept('--no-single-db') do |arg|
      @yardoc.parse_arguments(arg)
      expect(Registry.single_object_db).to be false
      Registry.single_object_db = nil
    end

    should_accept('-c', '--use-cache') do |arg|
      @yardoc.parse_arguments(arg)
      expect(@yardoc.use_cache).to be true
    end

    should_accept('--no-cache') do |arg|
      @yardoc.parse_arguments(arg)
      expect(@yardoc.use_cache).to be false
    end

    should_accept('--yardopts') do |arg|
      @yardoc = CLI::Yardoc.new
      @yardoc.use_document_file = false
      expect(@yardoc).to receive(:yardopts).at_least(1).times.and_return([])
      @yardoc.parse_arguments(arg)
      expect(@yardoc.use_yardopts_file).to be true
      @yardoc.parse_arguments('--no-yardopts', arg)
      expect(@yardoc.use_yardopts_file).to be true
    end

    should_accept('--yardopts with filename') do |_arg|
      @yardoc = CLI::Yardoc.new
      expect(File).to receive(:read_binary).with('.foobar').and_return('')
      @yardoc.use_document_file = false
      @yardoc.parse_arguments('--yardopts', '.foobar')
      expect(@yardoc.use_yardopts_file).to be true
      expect(@yardoc.options_file).to eq '.foobar'
    end

    should_accept('--no-yardopts') do |arg|
      @yardoc = CLI::Yardoc.new
      @yardoc.use_document_file = false
      expect(@yardoc).not_to receive(:yardopts)
      @yardoc.parse_arguments(arg)
      expect(@yardoc.use_yardopts_file).to be false
      @yardoc.parse_arguments('--yardopts', arg)
      expect(@yardoc.use_yardopts_file).to be false
    end

    should_accept('--document') do |arg|
      @yardoc = CLI::Yardoc.new
      @yardoc.use_yardopts_file = false
      expect(@yardoc).to receive(:support_rdoc_document_file!).and_return([])
      @yardoc.parse_arguments('--no-document', arg)
      expect(@yardoc.use_document_file).to be true
    end

    should_accept('--no-document') do |arg|
      @yardoc = CLI::Yardoc.new
      @yardoc.use_yardopts_file = false
      expect(@yardoc).not_to receive(:support_rdoc_document_file!)
      @yardoc.parse_arguments('--document', arg)
      expect(@yardoc.use_document_file).to be false
    end

    should_accept('-b', '--db') do |arg|
      @yardoc.parse_arguments(arg, 'test')
      expect(Registry.yardoc_file).to eq 'test'
      Registry.yardoc_file = '.yardoc'
    end

    should_accept('-n', '--no-output') do |arg|
      expect(Templates::Engine).not_to receive(:generate)
      @yardoc.run(arg)
    end

    should_accept('--exclude') do |arg|
      expect(YARD).to receive(:parse).with(['a'], ['nota', 'b'])
      @yardoc.run(arg, 'nota', arg, 'b', 'a')
    end

    should_accept('--no-save') do |arg|
      expect(YARD).to receive(:parse)
      expect(Registry).not_to receive(:save)
      @yardoc.run(arg)
    end

    # This example relies on processing Yard's own documentation, and with
    # --fail-on-warning option on, will raise a SystemExit error if building
    # that documentation produces any one warning.
    #
    # Unless handled, it will cause immediate and abnormal process exit,
    # without running remaining tests, and with non-successful exit status.
    #
    # While suppressing exceptions is generally a bad practice and against this
    # project's style guide, here it is well advocated,
    # hence Lint/HandleExceptions cop is disabled.
    should_accept('--fail-on-warning') do |arg|
      expect(YARD).to receive(:parse)
      begin
        @yardoc.run(arg)
      rescue SystemExit # rubocop:disable Lint/HandleExceptions
      end
    end
  end

  describe "Output options" do
    it "accepts --title" do
      @yardoc.parse_arguments('--title', 'hello world')
      expect(@yardoc.options.title).to eq 'hello world'
    end

    it "allows --title to have multiple spaces in .yardopts" do
      expect(File).to receive(:read_binary).with("test").and_return("--title \"Foo Bar\"")
      @yardoc.options_file = "test"
      @yardoc.use_yardopts_file = true
      @yardoc.run
      expect(@yardoc.options.title).to eq "Foo Bar"
    end

    it "aliases --main to the --readme flag" do
      Dir.chdir(File.join(File.dirname(__FILE__), '..', '..')) do
        @yardoc.parse_arguments('--main', 'README.md')
        expect(@yardoc.options.readme).to eq CodeObjects::ExtraFileObject.new('README.md', '')
      end
    end

    it "selects a markup provider when --markup-provider or -mp is set" do
      @yardoc.parse_arguments("-M", "test")
      expect(@yardoc.options.markup_provider).to eq :test
      @yardoc.parse_arguments("--markup-provider", "test2")
      expect(@yardoc.options.markup_provider).to eq :test2
    end

    it "selects a markup format when -m is set" do
      expect(@yardoc).to receive(:verify_markup_options).and_return(true)
      @yardoc.generate = true
      @yardoc.parse_arguments('-m', 'markdown')
      expect(@yardoc.options.markup).to eq :markdown
    end

    it "accepts --default-return" do
      @yardoc.parse_arguments(*%w(--default-return XYZ))
      expect(@yardoc.options.default_return).to eq "XYZ"
    end

    it "allows --hide-void-return to be set" do
      @yardoc.parse_arguments(*%w(--hide-void-return))
      expect(@yardoc.options.hide_void_return).to be true
    end

    it "accepts --embed-mixins" do
      @yardoc.parse_arguments(*%w(--embed-mixins))
      expect(@yardoc.options.embed_mixins).to eq ['*']
    end

    it "accepts --embed-mixin MODULE" do
      @yardoc.parse_arguments(*%w(--embed-mixin MyModule))
      expect(@yardoc.options.embed_mixins).to eq ['MyModule']
    end

    it "generates all objects with --use-cache" do
      expect(YARD).to receive(:parse)
      expect(Registry).to receive(:load)
      expect(Registry).to receive(:load_all)
      allow(@yardoc).to receive(:generate).and_return(true)
      @yardoc.run(*%w(--use-cache))
    end

    it "does not print statistics with --no-stats" do
      allow(@yardoc).to receive(:statistics).and_return(false)
      expect(CLI::Stats).not_to receive(:new)
      @yardoc.run(*%w(--no-stats))
    end

    it "disables progress bar with --no-progress" do
      old = log.show_progress
      log.show_progress = true
      @yardoc.run(*%w(--no-progress))
      expect(log.show_progress).to eq false
      log.show_progress = old
    end

    describe "--asset" do
      before do
        @yardoc.generate = true
        allow(@yardoc).to receive(:run_generate)
      end

      it "copies assets to output directory" do
        expect(FileUtils).to receive(:cp_r).with('a', 'doc/a')
        @yardoc.run(*%w(--asset a))
        expect(@yardoc.assets).to eq('a' => 'a')
      end

      it "allows multiple --asset options" do
        expect(FileUtils).to receive(:cp_r).with('a', 'doc/a')
        expect(FileUtils).to receive(:cp_r).with('b', 'doc/b')
        @yardoc.run(*%w(--asset a --asset b))
        expect(@yardoc.assets).to eq('a' => 'a', 'b' => 'b')
      end

      it "does not allow from or to to refer to a path above current path" do
        expect(log).to receive(:warn).exactly(4).times.with(/invalid/i)
        @yardoc.run(*%w(--asset ../../../etc/passwd))
        expect(@yardoc.assets).to be_empty
        @yardoc.run(*%w(--asset a/b/c/d/../../../../../../etc/passwd))
        expect(@yardoc.assets).to be_empty
        @yardoc.run(*%w(--asset /etc/passwd))
        expect(@yardoc.assets).to be_empty
        @yardoc.run(*%w(--asset normal:/etc/passwd))
        expect(@yardoc.assets).to be_empty
      end

      it "allows from:to syntax" do
        expect(FileUtils).to receive(:cp_r).with(%r{foo(\/\.)?}, 'doc/bar')
        @yardoc.run(*%w(--asset foo:bar))
        expect(@yardoc.assets).to eq('foo' => 'bar')
      end

      it "does not put from inside of to/ if from is a directory" do
        begin
          from = 'tmp_foo'
          to = 'tmp_bar'
          full_to = File.join(File.dirname(__FILE__), to)
          FileUtils.mkdir_p(from)
          @yardoc.options.serializer.basepath = File.dirname(__FILE__)
          @yardoc.run("--asset", "#{from}:#{to}")
          @yardoc.run("--asset", "#{from}:#{to}")
          expect(File.directory?(full_to)).to be true
          expect(File.directory?(File.join(full_to, 'tmp_foo'))).to be false
        ensure
          FileUtils.rm_rf(from)
          FileUtils.rm_rf(full_to)
        end
      end
    end

    describe "--locale" do
      it "applies specified locale to all extra file objects" do
        allow(File).to receive(:read).with('extra_file1').and_return('')
        allow(File).to receive(:read).with('extra_file2').and_return('')

        extra_file_object1 = CodeObjects::ExtraFileObject.new('extra_file1')
        extra_file_object2 = CodeObjects::ExtraFileObject.new('extra_file2')
        expect(extra_file_object1).to receive(:locale=).with('fr')
        expect(extra_file_object2).to receive(:locale=).with('fr')

        allow(CodeObjects::ExtraFileObject).to receive(:new).with('extra_file1').and_return(extra_file_object1)
        allow(CodeObjects::ExtraFileObject).to receive(:new).with('extra_file2').and_return(extra_file_object2)
        allow(Dir).to receive(:glob).with('README{,*[^~]}').and_return([])
        allow(File).to receive(:file?).with('extra_file1').and_return(true)
        allow(File).to receive(:file?).with('extra_file2').and_return(true)
        @yardoc.run('--locale=fr', '-', 'extra_file1', 'extra_file2')
      end
    end

    describe "--po-dir" do
      it "sets Registry.po_dir" do
        expect(Registry).to receive(:po_dir=).with("locale")
        @yardoc.run('--po-dir=locale')
      end
    end
  end

  describe "--[no-]api" do
    before { Registry.clear }

    it "allows --api name" do
      YARD.parse_string <<-eof
        # @api private
        class Foo; end
        # @api public
        class Bar; end
        class Baz; end
      eof
      @yardoc.run('--api', 'private')
      expect(@yardoc.options.verifier.run(Registry.all)).to eq [P('Foo')]
    end

    it "allows multiple --api's to all be shown" do
      YARD.parse_string <<-eof
        # @api private
        class Foo; end
        # @api public
        class Bar; end
        class Baz; end
      eof
      @yardoc.run('--api', 'private', '--api', 'public')
      expect(@yardoc.options.verifier.run(Registry.all).
        sort_by(&:path)).to eq [P('Bar'), P('Foo')]
    end

    it "allows --no-api to specify objects with no @api tag" do
      YARD.parse_string <<-eof
        # @api private
        class Foo; end
        # @api public
        class Bar; end
        class Baz; end
      eof
      @yardoc.run('--api', '')
      expect(@yardoc.options.verifier.run(Registry.all)).to eq [P('Baz')]
      @yardoc.options.verifier = Verifier.new
      @yardoc.run('--no-api')
      expect(@yardoc.options.verifier.run(Registry.all)).to eq [P('Baz')]
    end

    it "allows --no-api to work with other --api switches" do
      YARD.parse_string <<-eof
        # @api private
        class Foo; end
        # @api public
        class Bar; end
        class Baz; end
      eof
      @yardoc.run('--no-api', '--api', 'public')
      expect(@yardoc.options.verifier.run(Registry.all).
        sort_by(&:path)).to eq [P('Bar'), P('Baz')]
    end

    it "ensures Ruby code cannot be used" do
      [':symbol', '42', '"; exit'].each do |ruby|
        @yardoc.options.verifier.expressions = []
        @yardoc.run('--api', ruby)
        expect(@yardoc.options.verifier.expressions[1]).to include(ruby.inspect)
      end
    end
  end

  describe "--hide-api option" do
    it "allows --hide-api to hide objects with api tags" do
      YARD.parse_string <<-eof
        # @api private
        class Foo; end
        class Bar; end
        class Baz; end
      eof
      @yardoc.run('--hide-api', 'private')
      expect(@yardoc.options.verifier.run(Registry.all).
        sort_by(&:path)).to eq [P('Bar'), P('Baz')]
    end

    it "allows --hide-api to work with --api" do
      YARD.parse_string <<-eof
        # @api private
        class Foo; end
        # @api public
        class Bar; end
        class Baz; end
      eof
      @yardoc.run('--api', 'public', '--hide-api', 'private')
      expect(@yardoc.options.verifier.run(Registry.all).
        sort_by(&:path)).to eq [P('Bar')]
    end
  end

  describe "--no-private option" do
    it "accepts --no-private" do
      obj = double(:object)
      expect(obj).to receive(:tag).ordered.with(:private).and_return(true)
      @yardoc.parse_arguments(*%w(--no-private))
      expect(@yardoc.options.verifier.call(obj)).to be false
    end

    it "hides object if namespace is @private with --no-private" do
      ns = double(:namespace, :type => :module)
      expect(ns).to receive(:tag).with(:private).and_return(true)
      obj = double(:object, :namespace => ns)
      expect(obj).to receive(:tag).with(:private).and_return(false)
      @yardoc.parse_arguments(*%w(--no-private))
      expect(@yardoc.options.verifier.call(obj)).to be false
    end

    it "does not call #tag on namespace if namespace is proxy with --no-private" do
      ns = double(:namespace)
      expect(ns).to receive(:is_a?).with(CodeObjects::Proxy).and_return(true)
      expect(ns).not_to receive(:tag)
      obj = double(:object, :type => :class, :namespace => ns, :visibility => :public)
      expect(obj).to receive(:tag).ordered.with(:private).and_return(false)
      @yardoc.parse_arguments(*%w(--no-private))
      expect(@yardoc.options.verifier.call(obj)).to be true
    end

    # @bug gh-197
    it "does not call #tag on namespace if namespace is proxy with --no-private" do
      Registry.clear
      YARD.parse_string "module Qux; class Foo::Bar; end; end"
      foobar = Registry.at('Foo::Bar')
      foobar.namespace.type = :module
      @yardoc.parse_arguments(*%w(--no-private))
      expect(@yardoc.options.verifier.call(foobar)).to be true
    end

    it "does not call #tag on proxy object" do # @bug gh-197
      @yardoc.parse_arguments(*%w(--no-private))
      expect(@yardoc.options.verifier.call(P('ProxyClass'))).to be true
    end

    it "hides methods inside a 'private' class/module with --no-private" do
      Registry.clear
      YARD.parse_string <<-eof
        # @private
        class ABC
          def foo; end
        end
      eof
      @yardoc.parse_arguments(*%w(--no-private))
      expect(@yardoc.options.verifier.call(Registry.at('ABC'))).to be false
      expect(@yardoc.options.verifier.call(Registry.at('ABC#foo'))).to be false
    end
  end

  describe ".yardopts and .document handling" do
    before do
      @yardoc.use_yardopts_file = true
    end

    it "searches for and uses yardopts file specified by #options_file" do
      expect(File).to receive(:read_binary).with("test").and_return("-o \n\nMYPATH\nFILE1 FILE2")
      @yardoc.use_document_file = false
      @yardoc.options_file = "test"
      @yardoc.run
      expect(@yardoc.options.serializer.options[:basepath]).to eq "MYPATH"
      expect(@yardoc.files).to eq ["FILE1", "FILE2"]
    end

    it "uses String#shell_split to split .yardopts tokens" do
      optsdata = String.new("foo bar")
      expect(optsdata).to receive(:shell_split)
      expect(File).to receive(:read_binary).with("test").and_return(optsdata)
      @yardoc.options_file = "test"
      @yardoc.run
    end

    it "allows opts specified in command line to override yardopts file" do
      expect(File).to receive(:read_binary).with(".yardopts").and_return("-o NOTMYPATH")
      @yardoc.run("-o", "MYPATH", "FILE")
      expect(@yardoc.options.serializer.options[:basepath]).to eq "MYPATH"
      expect(@yardoc.files).to eq ["FILE"]
    end

    it "loads the RDoc .document file if found" do
      expect(File).to receive(:read_binary).with(".yardopts").and_return("-o NOTMYPATH")
      @yardoc.use_document_file = true
      allow(@yardoc).to receive(:support_rdoc_document_file!).and_return(["FILE2", "FILE3"])
      @yardoc.run("-o", "MYPATH", "FILE1")
      expect(@yardoc.options.serializer.options[:basepath]).to eq "MYPATH"
      expect(@yardoc.files).to eq ["FILE2", "FILE3", "FILE1"]
    end
  end

  describe "Query options" do
    after { Registry.clear }

    it "hides private constants in with default visibilities" do
      classobj = CodeObjects::ClassObject.new(:root, :Foo) {|o| o.visibility = :private }
      @yardoc.run
      expect(@yardoc.options.verifier.run([classobj])).to eq []
    end

    it "sets up visibility rules as verifier" do
      methobj = CodeObjects::MethodObject.new(:root, :test) {|o| o.visibility = :private }
      expect(File).to receive(:read_binary).with("test").and_return("--private")
      @yardoc.use_yardopts_file = true
      @yardoc.options_file = "test"
      @yardoc.run
      expect(@yardoc.options.verifier.call(methobj)).to be true
    end

    it "accepts a --query" do
      @yardoc.parse_arguments(*%w(--query @return))
      expect(@yardoc.options.verifier).to be_a(Verifier)
    end

    it "accepts multiple --query arguments" do
      obj = double(:object)
      expect(obj).to receive(:tag).ordered.with('return').and_return(true)
      expect(obj).to receive(:tag).ordered.with('tag').and_return(false)
      @yardoc.parse_arguments(*%w(--query @return --query @tag))
      expect(@yardoc.options.verifier).to be_a(Verifier)
      expect(@yardoc.options.verifier.call(obj)).to be false
    end
  end

  describe "Extra file arguments" do
    def expect_extra_files_valid(obj, validity_hash = {})
      validity_hash.each do |filename, valid|
        expect(obj).to receive(:extra_file_valid?).with(filename).and_return(valid)
      end
    end

    it "accepts extra files if specified after '-' with source files" do
      expect(Dir).to receive(:glob).with('README{,*[^~]}').and_return([])
      expect(File).to receive(:file?).with('extra_file1').and_return(true)
      expect(File).to receive(:file?).with('extra_file2').and_return(true)
      expect(File).to receive(:read).with('extra_file1').and_return('')
      expect(File).to receive(:read).with('extra_file2').and_return('')
      @yardoc.parse_arguments(*%w(file1 file2 - extra_file1 extra_file2))
      expect(@yardoc.files).to eq %w(file1 file2)
      expect(@yardoc.options.files).to eq(
        [CodeObjects::ExtraFileObject.new('extra_file1', ''),
          CodeObjects::ExtraFileObject.new('extra_file2', '')]
      )
    end

    it "accepts files section only containing extra files" do
      expect(Dir).to receive(:glob).with('README{,*[^~]}').and_return([])
      @yardoc.parse_arguments(*%w(- LICENSE))
      expect(@yardoc.files).to eq Parser::SourceParser::DEFAULT_PATH_GLOB
      expect(@yardoc.options.files).to eq [CodeObjects::ExtraFileObject.new('LICENSE', '')]
    end

    it "accepts globs as extra files" do
      expect(Dir).to receive(:glob).with('README{,*[^~]}').and_return []
      expect(Dir).to receive(:glob).with('*.txt').and_return ['a.txt', 'b.txt']
      expect(File).to receive(:read).with('a.txt').and_return('')
      expect(File).to receive(:read).with('b.txt').and_return('')
      expect(File).to receive(:file?).with('a.txt').and_return(true)
      expect(File).to receive(:file?).with('b.txt').and_return(true)
      @yardoc.parse_arguments(*%w(file1 file2 - *.txt))
      expect(@yardoc.files).to eq %w(file1 file2)
      expect(@yardoc.options.files).to eq(
        [CodeObjects::ExtraFileObject.new('a.txt', ''),
          CodeObjects::ExtraFileObject.new('b.txt', '')]
      )
    end

    it "warns if extra file is not found" do
      expect(log).to receive(:warn).with(/Could not find file: UNKNOWN/)
      @yardoc.parse_arguments(*%w(- UNKNOWN))
    end

    it "warns if readme file is not found" do
      expect(log).to receive(:warn).with(/Could not find file: UNKNOWN/)
      @yardoc.parse_arguments(*%w(-r UNKNOWN))
    end

    it "warns on absolute paths in extra files" do
      expect(log).to receive(:warn).with(%r{Invalid file: /path/to/file})
      @yardoc.parse_arguments(*%w(- /path/to/file))
    end

    it "warns on absolute paths in readme" do
      expect(log).to receive(:warn).with(%r{Invalid file: /path/to/file})
      @yardoc.parse_arguments(*%w(-r /path/to/file))
    end

    it "uses first file as readme if no readme is specified when using --one-file" do
      expect(Dir).to receive(:glob).with('README{,*[^~]}').and_return []
      expect(Dir).to receive(:glob).with('lib/*.rb').and_return(['lib/foo.rb'])
      expect_extra_files_valid(@yardoc, 'lib/foo.rb' => true)
      expect(File).to receive(:read).with('lib/foo.rb').and_return('')
      @yardoc.parse_arguments(*%w(--one-file lib/*.rb))
      expect(@yardoc.options.readme).to eq CodeObjects::ExtraFileObject.new('lib/foo.rb', '')
    end

    it "uses no readme if files is empty and no readme is specified when using --one-file" do
      expect(Dir).to receive(:glob).with('README{,*[^~]}').and_return []
      @yardoc.parse_arguments '--one-file', ''
      expect(@yardoc.options.readme).to be_nil
    end

    it "uses readme if it exists when using --one-file" do
      expect(Dir).to receive(:glob).with('README{,*[^~]}').and_return ['README']
      expect_extra_files_valid(@yardoc, 'README' => true)
      expect_extra_files_valid(@yardoc, 'README' => true)
      expect(File).to receive(:read).with('README').and_return('')
      @yardoc.parse_arguments(*%w(--one-file lib/*.rb))
      expect(@yardoc.options.readme).to eq CodeObjects::ExtraFileObject.new('README', '')
    end

    it "selects readme with no file extension over readme with file extension" do
      expect(Dir).to receive(:glob).with('README{,*[^~]}').and_return ['README.md', 'README-DEV', 'README']
      expect_extra_files_valid(@yardoc, 'README.md' => true, 'README-DEV' => true, 'README' => true)
      expect_extra_files_valid(@yardoc, 'README' => true)
      expect(File).to receive(:read).with('README').and_return('')
      @yardoc.parse_arguments
      expect(@yardoc.options.readme).to eq CodeObjects::ExtraFileObject.new('README', '')
    end

    it "selects readme with no suffix over readme with hyphenated suffix" do
      expect(Dir).to receive(:glob).with('README{,*[^~]}').and_return ['README-fr.md', 'README.long-extension', 'README-de.md']
      expect_extra_files_valid(@yardoc, 'README-fr.md' => true, 'README.long-extension'=> true, 'README-de.md' => true)
      expect_extra_files_valid(@yardoc, 'README.long-extension'=> true)
      expect(File).to receive(:read).with('README.long-extension').and_return('')
      @yardoc.parse_arguments
      expect(@yardoc.options.readme).to eq CodeObjects::ExtraFileObject.new('README.long-extension', '')
    end

    it "selects readme with no suffix over readme with dotted suffix" do
      expect(Dir).to receive(:glob).with('README{,*[^~]}').and_return ['README.fr.md', 'README.md', 'README.de.md']
      expect(File).to receive(:read).with('README.md').and_return('')
      @yardoc.parse_arguments
      expect(@yardoc.options.readme).to eq CodeObjects::ExtraFileObject.new('README.md', '')
    end

    it "selects first readme from lexically sorted list" do
      expect(Dir).to receive(:glob).with('README{,*[^~]}').and_return ['README-fr.md', 'README-de.md']
      expect_extra_files_valid(@yardoc, 'README-fr.md'=> true, 'README-de.md' => true)
      expect_extra_files_valid(@yardoc, 'README-de.md' => true)
      expect(File).to receive(:read).with('README-de.md').and_return('')
      @yardoc.parse_arguments
      expect(@yardoc.options.readme).to eq CodeObjects::ExtraFileObject.new('README-de.md', '')
    end

    it "selects readme that exists over a readme that does not" do
      expect(Dir).to receive(:glob).with('README{,*[^~]}').and_return ['README.fr.md', 'README.md', 'README.de.md']
      expect_extra_files_valid(@yardoc, 'README.fr.md'=> true, 'README.md' => false, 'README.de.md' => false)
      expect_extra_files_valid(@yardoc, 'README.fr.md'=> true)
      expect(File).to receive(:read).with('README.fr.md').and_return('')
      @yardoc.parse_arguments
      expect(@yardoc.options.readme).to eq CodeObjects::ExtraFileObject.new('README.fr.md', '')
    end

    it "does not allow US-ASCII charset when using --one-file" do
      ienc = Encoding.default_internal
      eenc = Encoding.default_external
      expect(log).to receive(:warn).with(/not compatible with US-ASCII.*using ASCII-8BIT/)
      @yardoc.parse_arguments(*%w(--one-file --charset us-ascii))
      expect(Encoding.default_internal.name).to eq 'ASCII-8BIT'
      expect(Encoding.default_external.name).to eq 'ASCII-8BIT'
      Encoding.default_internal = ienc
      Encoding.default_external = eenc
    end if defined?(::Encoding)
  end

  describe "Source file arguments" do
    it "accepts no params and parse {lib,app}/**/*.rb ext/**/*.c" do
      @yardoc.parse_arguments
      expect(@yardoc.files).to eq Parser::SourceParser::DEFAULT_PATH_GLOB
    end
  end

  describe "Tags options" do
    def tag_created(switch, factory_method)
      visible_tags = double(:visible_tags)
      expect(visible_tags).to receive(:|).ordered.with([:foo])
      expect(visible_tags).to receive(:-).ordered.with([]).and_return(visible_tags)
      expect(Tags::Library).to receive(:define_tag).with('Foo', :foo, factory_method)
      allow(Tags::Library).to receive(:visible_tags=)
      expect(Tags::Library).to receive(:visible_tags).at_least(1).times.and_return(visible_tags)
      @yardoc.parse_arguments("--#{switch}-tag", 'foo')
    end

    def tag_hidden(tag)
      visible_tags = double(:visible_tags)
      expect(visible_tags).to receive(:|).ordered.with([tag])
      expect(visible_tags).to receive(:-).ordered.with([tag]).and_return([])
      expect(Tags::Library).to receive(:define_tag).with(tag.to_s.capitalize, tag, nil)
      allow(Tags::Library).to receive(:visible_tags=)
      expect(Tags::Library).to receive(:visible_tags).at_least(1).times.and_return(visible_tags)
    end

    it "accepts --tag" do
      expect(Tags::Library).to receive(:define_tag).with('Title of Foo', :foo, nil)
      @yardoc.parse_arguments('--tag', 'foo:Title of Foo')
    end

    it "accepts --tag without title (and default to capitalized tag name)" do
      expect(Tags::Library).to receive(:define_tag).with('Foo', :foo, nil)
      @yardoc.parse_arguments('--tag', 'foo')
    end

    it "only lists tag once if declared twice" do
      visible_tags = []
      allow(Tags::Library).to receive(:define_tag)
      allow(Tags::Library).to receive(:visible_tags).and_return([:foo])
      allow(Tags::Library).to receive(:visible_tags=) {|value| visible_tags = value }
      @yardoc.parse_arguments('--tag', 'foo', '--tag', 'foo')
      expect(visible_tags).to eq [:foo]
    end

    it "accepts --type-tag" do
      tag_created 'type', :with_types
    end

    it "accepts --type-name-tag" do
      tag_created 'type-name', :with_types_and_name
    end

    it "accepts --name-tag" do
      tag_created 'name', :with_name
    end

    it "accepts --title-tag" do
      tag_created 'title', :with_title_and_text
    end

    it "accepts --hide-tag before tag is listed" do
      tag_hidden(:anewfoo)
      @yardoc.parse_arguments('--hide-tag', 'anewfoo', '--tag', 'anewfoo')
    end

    it "accepts --hide-tag after tag is listed" do
      tag_hidden(:anewfoo2)
      @yardoc.parse_arguments('--tag', 'anewfoo2', '--hide-tag', 'anewfoo2')
    end

    it "accepts --transitive-tag" do
      @yardoc.parse_arguments('--transitive-tag', 'foo')
      expect(Tags::Library.transitive_tags).to include(:foo)
    end

    it "accepts --non-transitive-tag" do
      Tags::Library.transitive_tags |= [:foo]
      @yardoc.parse_arguments('--non-transitive-tag', 'foo')
      expect(Tags::Library.transitive_tags).not_to include(:foo)
    end
  end

  describe "Safe mode" do
    before do
      allow(YARD::Config).to receive(:options).and_return(:safe_mode => true)
    end

    it "does not allow --load or -e in safe mode" do
      expect(@yardoc).not_to receive(:require)
      @yardoc.run('--load', 'foo')
      @yardoc.run('-e', 'foo')
    end

    it "does not allow --query in safe mode" do
      @yardoc.run('--query', 'foo')
      expect(@yardoc.options.verifier.expressions).not_to include("foo")
    end

    it "does not allow modifying the template paths" do
      expect(YARD::Templates::Engine).not_to receive(:register_template_path)
      @yardoc.run('-p', 'foo')
      @yardoc.run('--template-path', 'foo')
    end
  end

  describe "Markup Loading" do
    it "loads rdoc markup if no markup is provided" do
      @yardoc.generate = true
      @yardoc.run
      expect(@yardoc.options.markup).to eq :rdoc
    end

    it "loads rdoc markup even when no output is specified" do
      @yardoc.parse_arguments('--no-output')
      expect(@yardoc.options.markup).to eq :rdoc
    end

    it "warns if rdoc cannot be loaded and fallback to :none" do
      mod = YARD::Templates::Helpers::MarkupHelper
      mod.clear_markup_cache
      expect(mod.const_get(:MARKUP_PROVIDERS)).to receive(:[]).with(:rdoc).and_return([{:lib => 'INVALID'}])
      expect(log).to receive(:warn).with(/Could not load default RDoc formatter/)
      allow(@yardoc).to receive(:generate) { @yardoc.options.files = []; true }
      @yardoc.run
      expect(@yardoc.options.markup).to eq :none
      mod.clear_markup_cache
    end

    it "returns an error immediately if markup for any files are missing" do
      mod = YARD::Templates::Helpers::MarkupHelper
      mod.clear_markup_cache
      expect(mod.const_get(:MARKUP_PROVIDERS)).to receive(:[]).with(:markdown).and_return([{:lib => 'INVALID'}])
      expect(log).to receive(:error).with(/Missing 'INVALID' gem for Markdown formatting/)
      files = [CodeObjects::ExtraFileObject.new('test.md', '')]
      allow(@yardoc).to receive(:generate) { @yardoc.options.files = files; true }
      @yardoc.run
      mod.clear_markup_cache
    end

    it "returns an error immediately if markup for any files are missing (file markup specified in attributes)" do
      mod = YARD::Templates::Helpers::MarkupHelper
      mod.clear_markup_cache
      expect(mod.const_get(:MARKUP_PROVIDERS)).to receive(:[]).with(:markdown).and_return([{:lib => 'INVALID'}])
      expect(log).to receive(:error).with(/Missing 'INVALID' gem for Markdown formatting/)
      files = [CodeObjects::ExtraFileObject.new('test', '# @markup markdown')]
      allow(@yardoc).to receive(:generate) { @yardoc.options.files = files; true }
      @yardoc.run
      mod.clear_markup_cache
    end
  end

  describe "#run" do
    it "parses arguments if run() is called" do
      expect(@yardoc).to receive(:parse_arguments)
      @yardoc.run
    end

    it "parses arguments if run(arg1, arg2, ...) is called" do
      expect(@yardoc).to receive(:parse_arguments)
      @yardoc.run('--private', '-p', 'foo')
    end

    it "does not parse arguments if run(nil) is called" do
      expect(@yardoc).not_to receive(:parse_arguments)
      @yardoc.run(nil)
    end

    it "creates processing lock if saving" do
      expect(Registry).to receive(:lock_for_writing).and_yield
      @yardoc.run
    end

    it "does not create processing lock if not saving" do
      expect(Registry).not_to receive(:lock_for_writing)
      @yardoc.run('--no-save')
    end

    context "with --fail-on-warning" do
      it "exits with error status code if a warning occurs" do
        allow(log).to receive(:warned).and_return(true)
        expect { @yardoc.run("--fail-on-warning") }.to raise_error(SystemExit) do |error|
          expect(error).not_to be_success
        end
      end

      it "does not exit if a warning does not occur" do
        allow(log).to receive(:warned).and_return(false)
        expect { @yardoc.run("--fail-on-warning") }.not_to raise_error
      end
    end
  end
end