# frozen_string_literal: true
require File.dirname(__FILE__) + "/spec_helper"

RSpec.describe YARD::Handlers::C::MethodHandler do
  it "registers methods" do
    parse_init <<-eof
      mFoo = rb_define_module("Foo");
      rb_define_method(mFoo, "bar", bar, 0);
    eof
    expect(Registry.at('Foo#bar')).not_to be nil
    expect(Registry.at('Foo#bar').visibility).to eq :public
  end

  it "registers private methods" do
    parse_init <<-eof
      mFoo = rb_define_module("Foo");
      rb_define_private_method(mFoo, "bar", bar, 0);
    eof
    expect(Registry.at('Foo#bar')).not_to be nil
    expect(Registry.at('Foo#bar').visibility).to eq :private
  end

  it "registers singleton methods" do
    parse_init <<-eof
      mFoo = rb_define_module("Foo");
      rb_define_singleton_method(mFoo, "bar", bar, 0);
    eof
    expect(Registry.at('Foo.bar')).not_to be nil
    expect(Registry.at('Foo.bar').visibility).to eq :public
  end

  it "registers module functions" do
    parse <<-eof
      /* DOCSTRING
       * @return [String] foo!
      */
      static VALUE bar(VALUE self) { x(); y(); z(); }

      void Init_Foo() {
        mFoo = rb_define_module("Foo");
        rb_define_module_function(mFoo, "bar", bar, 0);
      }
    eof
    bar_c = Registry.at('Foo.bar')
    bar_i = Registry.at('Foo#bar')
    expect(bar_c).to be_module_function
    expect(bar_c.visibility).to eq :public
    expect(bar_c.docstring).to eq "DOCSTRING"
    expect(bar_c.tag(:return).object).to eq bar_c
    expect(bar_c.source).to eq "static VALUE bar(VALUE self) { x(); y(); z(); }"
    expect(bar_i).not_to be_module_function
    expect(bar_i.visibility).to eq :private
    expect(bar_i.docstring).to eq "DOCSTRING"
    expect(bar_i.tag(:return).object).to eq bar_i
    expect(bar_i.source).to eq bar_c.source
  end

  it "registers global functions into Kernel" do
    parse_init 'rb_define_global_function("bar", bar, 0);'
    expect(Registry.at('Kernel#bar')).not_to be nil
  end

  it "looks for symbol containing method source" do
    parse <<-eof
      static VALUE foo(VALUE self) { x(); y(); z(); }
      VALUE bar() { a(); b(); c(); }
      void Init_Foo() {
        mFoo = rb_define_module("Foo");
        rb_define_method(mFoo, "foo", foo, 0);
        rb_define_method(mFoo, "bar", bar, 0);
      }
    eof
    foo = Registry.at('Foo#foo')
    bar = Registry.at('Foo#bar')
    expect(foo.source).to eq "static VALUE foo(VALUE self) { x(); y(); z(); }"
    expect(foo.file).to eq '(stdin)'
    expect(foo.line).to eq 1
    expect(bar.source).to eq "VALUE bar() { a(); b(); c(); }"
    expect(bar.file).to eq '(stdin)'
    expect(bar.line).to eq 2
  end

  it "finds docstrings attached to method symbols" do
    parse <<-eof
      /* DOCSTRING */
      static VALUE foo(VALUE self) { x(); y(); z(); }
      void Init_Foo() {
        mFoo = rb_define_module("Foo");
        rb_define_method(mFoo, "foo", foo, 0);
      }
    eof
    foo = Registry.at('Foo#foo')
    expect(foo.docstring).to eq 'DOCSTRING'
  end

  it "uses declaration comments as docstring if there are no others" do
    parse <<-eof
      static VALUE foo(VALUE self) { x(); y(); z(); }
      void Init_Foo() {
        mFoo = rb_define_module("Foo");
        /* DOCSTRING */
        rb_define_method(mFoo, "foo", foo, 0);
        // DOCSTRING!
        rb_define_method(mFoo, "bar", bar, 0);
      }
    eof
    foo = Registry.at('Foo#foo')
    expect(foo.docstring).to eq 'DOCSTRING'
    bar = Registry.at('Foo#bar')
    expect(bar.docstring).to eq 'DOCSTRING!'
  end

  it "looks for symbols in other file" do
    other = <<-eof
      /* DOCSTRING! */
      static VALUE foo() { x(); }
    eof
    expect(File).to receive(:read).with('other.c').and_return(other)
    parse <<-eof
      void Init_Foo() {
        mFoo = rb_define_module("Foo");
        rb_define_method(mFoo, "foo", foo, 0); // in other.c
      }
    eof
    foo = Registry.at('Foo#foo')
    expect(foo.docstring).to eq 'DOCSTRING!'
    expect(foo.file).to eq 'other.c'
    expect(foo.line).to eq 2
    expect(foo.source).to eq 'static VALUE foo() { x(); }'
  end

  it "allows extra file to include /'s and other filename characters" do
    expect(File).to receive(:read).at_least(1).times.with('ext/a-file.c').and_return(<<-eof)
      /* FOO */
      VALUE foo(VALUE x) { int value = x; }

      /* BAR */
      VALUE bar(VALUE x) { int value = x; }
    eof
    parse_init <<-eof
      rb_define_method(rb_cFoo, "foo", foo, 1); /* in ext/a-file.c */
      rb_define_global_function("bar", bar, 1); /* in ext/a-file.c */
    eof
    expect(Registry.at('Foo#foo').docstring).to eq 'FOO'
    expect(Registry.at('Kernel#bar').docstring).to eq 'BAR'
  end

  it "warns if other file can't be found" do
    expect(log).to receive(:warn).with(/Missing source file `other.c' when parsing Foo#foo/)
    parse <<-eof
      void Init_Foo() {
        mFoo = rb_define_module("Foo");
        rb_define_method(mFoo, "foo", foo, 0); // in other.c
      }
    eof
  end

  it "looks at override comments for docstring" do
    parse <<-eof
      /* Document-method: Foo::foo
       * Document-method: new
       * Document-method: Foo::Bar#baz
       * Foo bar!
       */

      // init comments
      void Init_Foo() {
        mFoo = rb_define_module("Foo");
        rb_define_method(mFoo, "foo", foo, 0);
        rb_define_method(mFoo, "initialize", foo, 0);
        mBar = rb_define_module_under(mFoo, "Bar");
        rb_define_method(mBar, "baz", foo, 0);
      }
    eof
    expect(Registry.at('Foo#foo').docstring).to eq 'Foo bar!'
    expect(Registry.at('Foo#initialize').docstring).to eq 'Foo bar!'
    expect(Registry.at('Foo::Bar#baz').docstring).to eq 'Foo bar!'
  end

  it "looks at overrides in other files" do
    other = <<-eof
      /* Document-method: Foo::foo
       * Document-method: new
       * Document-method: Foo::Bar#baz
       * Foo bar!
       */
    eof
    expect(File).to receive(:read).with('foo/bar/other.c').and_return(other)
    src = <<-eof
      void Init_Foo() {
        mFoo = rb_define_module("Foo");
        rb_define_method(mFoo, "foo", foo, 0); // in foo/bar/other.c
        rb_define_method(mFoo, "initialize", foo, 0); // in foo/bar/other.c
        mBar = rb_define_module_under(mFoo, "Bar"); // in foo/bar/other.c
        rb_define_method(mBar, "baz", foo, 0); // in foo/bar/other.c
      }
    eof
    parse(src, 'foo/bar/baz/init.c')
    expect(Registry.at('Foo#foo').docstring).to eq 'Foo bar!'
    expect(Registry.at('Foo#initialize').docstring).to eq 'Foo bar!'
    expect(Registry.at('Foo::Bar#baz').docstring).to eq 'Foo bar!'
  end

  it "adds return tag on methods ending in '?'" do
    parse <<-eof
      /* DOCSTRING */
      static VALUE foo(VALUE self) { x(); y(); z(); }
      void Init_Foo() {
        mFoo = rb_define_module("Foo");
        rb_define_method(mFoo, "foo?", foo, 0);
      }
    eof
    foo = Registry.at('Foo#foo?')
    expect(foo.docstring).to eq 'DOCSTRING'
    expect(foo.tag(:return).types).to eq ['Boolean']
    expect(foo.tags(:return).size).to eq 1
  end

  it "does not add return tag if return tags exist" do
    parse <<-eof
      // @return [String] foo
      static VALUE foo(VALUE self) { x(); y(); z(); }
      void Init_Foo() {
        mFoo = rb_define_module("Foo");
        rb_define_method(mFoo, "foo?", foo, 0);
      }
    eof
    foo = Registry.at('Foo#foo?')
    expect(foo.tag(:return).types).to eq ['String']
    expect(foo.tags(:return).size).to eq 1
  end

  it "handles casted method names" do
    parse_init <<-eof
      mFoo = rb_define_module("Foo");
      rb_define_method(mFoo, "bar", (METHOD)bar, 0);
      rb_define_global_function("baz", (METHOD)baz, 0);
    eof
    expect(Registry.at('Foo#bar')).not_to be nil
    expect(Registry.at('Kernel#baz')).not_to be nil
  end

  it "extracts at regular method parameters from C function signatures" do
    parse <<-eof
      static VALUE noargs_func(VALUE self) { return Qnil; }
      static VALUE twoargs_func(VALUE self, VALUE a, VALUE b) { return a; }
      void Init_Foo() {
        mFoo = rb_define_module("Foo");
        rb_define_method(mFoo, "noargs", noargs_func, 0);
        rb_define_method(mFoo, "twoargs", twoargs_func, 2);
      }
    eof
    expect(Registry.at('Foo#noargs').parameters).to be_empty
    expect(Registry.at('Foo#twoargs').parameters).to eq [['a', nil], ['b', nil]]
  end

  it "extracts at varargs method parameters from C function signatures" do
    parse <<-eof
      static VALUE varargs_func(int argc, VALUE *argv, VALUE self) { return self; }
      /* let's see if parser is robust in the face of strange spacing */
      static VALUE varargs_func2(	int 	argc ,	VALUE
	* 	 argv  ,VALUE  self	)

      {return self;}
      void Init_Foo() {
        mFoo = rb_define_module("Foo");
        rb_define_method(mFoo, "varargs", varargs_func, -1);
        rb_define_method(	mFoo	,"varargs2",varargs_func2		,-1);
      }
    eof
    expect(Registry.at('Foo#varargs').parameters).to eq [['*args', nil]]
    expect(Registry.at('Foo#varargs2').parameters).to eq [['*args', nil]]
  end

  it "is not too strict or too loose about matching override comments to methods" do
    parse <<-eof
      /* Document-method: Foo::foo
       * Document-method: new
       * Document-method: Foo::Bar#baz
       * Foo bar!
       */

      void Init_Foo() {
        mFoo = rb_define_module("Foo");
        mBar = rb_define_module_under(mFoo, "Bar");

        rb_define_method(mFoo, "foo", foo, 0);
        rb_define_singleton_method(mFoo, "foo", foo, 0);
        rb_define_method(mBar, "foo", foo, 0);
        rb_define_singleton_method(mBar, "foo", foo, 0);

        rb_define_method(mFoo, "initialize", foo, 0);
        rb_define_method(mBar, "initialize", foo, 0);

        rb_define_method(mFoo, "baz", foo, 0);
        rb_define_singleton_method(mFoo, "baz", foo, 0);
        rb_define_method(mBar, "baz", foo, 0);
        rb_define_singleton_method(mBar, "baz", foo, 0);
      }
    eof
    expect(Registry.at('Foo#foo').docstring).to eq 'Foo bar!'
    expect(Registry.at('Foo.foo').docstring).to eq 'Foo bar!'
    expect(Registry.at('Foo::Bar#foo').docstring).to be_empty
    expect(Registry.at('Foo::Bar.foo').docstring).to be_empty
    expect(Registry.at('Foo#initialize').docstring).to eq 'Foo bar!'
    expect(Registry.at('Foo::Bar#initialize').docstring).to eq 'Foo bar!'
    expect(Registry.at('Foo#baz').docstring).to be_empty
    expect(Registry.at('Foo.baz').docstring).to be_empty
    expect(Registry.at('Foo::Bar#baz').docstring).to eq 'Foo bar!'
    expect(Registry.at('Foo::Bar.baz').docstring).to be_empty
  end

  it "recognizes core Ruby classes and modules provided by ruby.h" do
    parse_init <<-eof
      rb_define_method(rb_cFixnum, "popcount", fix_popcount, 0);
      rb_define_private_method(rb_mKernel, "pp", obj_pp, 0);
      rb_define_method(rb_mEnumerable, "to_hash", enum_to_hash, 0);
    eof
    expect(Registry.at('Fixnum').type).to eq :class
    expect(Registry.at('Fixnum#popcount').type).to eq :method
    expect(Registry.at('Object').type).to eq :class
    # Methods defined on Kernel are treated as if they were defined on Object
    expect(Registry.at('Object#pp').type).to eq :method
    expect(Registry.at('Enumerable').type).to eq :module
    expect(Registry.at('Enumerable#to_hash').type).to eq :method
  end
end
