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

RSpec.describe "YARD::Handlers::Ruby::#{LEGACY_PARSER ? "Legacy::" : ""}DSLHandler" do
  before(:all) { parse_file :dsl_handler_001, __FILE__ }

  it "creates a readable attribute when @!attribute r is found" do
    obj = Registry.at('Foo#attr1')
    expect(obj).not_to be nil
    expect(obj).to be_reader
    expect(obj.tag(:return).types).to eq ['Numeric']
    expect(Registry.at('Foo#attr1=')).to be nil
  end

  it "creates a writable attribute when @!attribute w is found" do
    obj = Registry.at('Foo#attr2=')
    expect(obj).not_to be nil
    expect(obj).to be_writer
    expect(Registry.at('Foo#attr2')).to be nil
  end

  it "defaults to readwrite @!attribute" do
    obj = Registry.at('Foo#attr3')
    expect(obj).not_to be nil
    expect(obj).to be_reader
    obj = Registry.at('Foo#attr3=')
    expect(obj).not_to be nil
    expect(obj).to be_writer
  end

  it "allows @!attribute to define alternate method name" do
    expect(Registry.at('Foo#attr4')).to be nil
    expect(Registry.at('Foo#custom')).not_to be nil
  end

  it "defaults to creating an instance method for any DSL method with special tags" do
    obj = Registry.at('Foo#implicit0')
    expect(obj).not_to be nil
    expect(obj.docstring).to eq "IMPLICIT METHOD!"
    expect(obj.tag(:return).types).to eq ['String']
  end

  it "recognizes implicit docstring when it has scope tag" do
    obj = Registry.at("Foo.implicit1")
    expect(obj).not_to be nil
    expect(obj.scope).to eq :class
  end

  it "recognizes implicit docstring when it has visibility tag" do
    obj = Registry.at("Foo#implicit2")
    expect(obj).not_to be nil
    expect(obj.visibility).to eq :protected
  end

  it "does not recognize implicit docstring with any other normal tag" do
    obj = Registry.at('Foo#implicit_invalid3')
    expect(obj).to be nil
  end

  it "sets the method name when using @!method" do
    obj = Registry.at('Foo.xyz')
    expect(obj).not_to be nil
    expect(obj.signature).to eq 'def xyz(a, b, c)'
    expect(obj.parameters).to eq [['a', nil], ['b', nil], ['c', nil]]
    expect(obj.source).to eq 'foo_bar'
    expect(obj.docstring).to eq 'The foo method'
  end

  it "recognizes methods with no attached source" do
    obj = Registry.at('Foo#no_src')
    expect(obj).not_to be nil
    expect(obj.signature).to eq 'def no_src'
    expect(obj.source).to eq ''
  end

  it "allows setting of @!scope" do
    expect(Registry.at('Foo.xyz').scope).to eq :class
  end

  it "creates module function if @!scope is module" do
    mod_c = Registry.at('Foo.modfunc1')
    mod_i = Registry.at('Foo#modfunc1')
    expect(mod_c.scope).to eq :class
    expect(mod_i.visibility).to eq :private
  end

  it "allows setting of @!visibility" do
    expect(Registry.at('Foo.xyz').visibility).to eq :protected
  end

  it "ignores DSL methods without tags" do
    expect(Registry.at('Foo#implicit_invalid')).to be nil
  end

  it "accepts a DSL method without tags if it has hash_flag (##)" do
    expect(Registry.at('Foo#implicit_valid')).not_to be nil
    expect(Registry.at('Foo#implicit_invalid2')).to be nil
  end

  it "allows creation of macros" do
    macro = CodeObjects::MacroObject.find('property')
    expect(macro).not_to be nil
    expect(macro).not_to be_attached
    expect(macro.method_object).to be nil
  end

  it "handles macros with no parameters to expand" do
    expect(Registry.at('Foo#none')).not_to be nil
    expect(Registry.at('Baz#none').signature).to eq 'def none(foo, bar)'
  end

  it "expands $N on method definitions" do
    expect(Registry.at('Foo#regular_meth').docstring).to eq 'a b c'
  end

  it "applies new macro docstrings on new objects" do
    obj = Registry.at('Foo#name')
    expect(obj).not_to be nil
    expect(obj.source).to eq 'property :name, String, :a, :b, :c'
    expect(obj.signature).to eq 'def name(a, b, c)'
    expect(obj.docstring).to eq 'A property that is awesome.'
    expect(obj.tag(:param).name).to eq 'a'
    expect(obj.tag(:param).text).to eq 'first parameter'
    expect(obj.tag(:return).types).to eq ['String']
    expect(obj.tag(:return).text).to eq 'the property name'
  end

  it "allows reuse of named macros" do
    obj = Registry.at('Foo#age')
    expect(obj).not_to be nil
    expect(obj.source).to eq 'property :age, Fixnum, :value'
    expect(obj.signature).to eq 'def age(value)'
    expect(obj.docstring).to eq 'A property that is awesome.'
    expect(obj.tag(:param).name).to eq 'value'
    expect(obj.tag(:param).text).to eq 'first parameter'
    expect(obj.tag(:return).types).to eq ['Fixnum']
    expect(obj.tag(:return).text).to eq 'the property age'
  end

  it "knows about method information on DSL with macro expansion" do
    expect(Registry.at('Foo#right_name')).not_to be nil
    expect(Registry.at('Foo#right_name').source).to eq 'implicit_with_different_method_name :wrong, :right'
    expect(Registry.at('Foo#wrong_name')).to be nil
  end

  it "uses attached macros" do
    macro = CodeObjects::MacroObject.find('parser')
    expect(macro.macro_data).to eq "@!method $1(opts = {})\n@return NOTHING!"
    expect(macro).not_to be nil
    expect(macro).to be_attached
    expect(macro.method_object).to eq P('Foo.parser')
    obj = Registry.at('Foo#c_parser')
    expect(obj).not_to be nil
    expect(obj.docstring).to eq ""
    expect(obj.signature).to eq "def c_parser(opts = {})"
    expect(obj.docstring.tag(:return).text).to eq "NOTHING!"
  end

  it "appends docstring on DSL method to attached macro" do
    obj = Registry.at('Foo#d_parser')
    expect(obj).not_to be nil
    expect(obj.docstring).to eq "Another docstring"
    expect(obj.signature).to eq "def d_parser(opts = {})"
    expect(obj.docstring.tag(:return).text).to eq "NOTHING!"
  end

  it "only uses attached macros on methods defined in inherited hierarchy" do
    expect(Registry.at('Bar#x_parser')).to be nil
    expect(Registry.at('Baz#y_parser')).not_to be nil
  end

  it "looks through mixins for attached macros" do
    meth = Registry.at('Baz#mixin_method')
    expect(meth).not_to be nil
    expect(meth.docstring).to eq 'DSL method mixin_method'
  end

  it "handles top-level DSL methods" do
    obj = Registry.at('#my_other_method')
    expect(obj).not_to be nil
    expect(obj.docstring).to eq "Docstring for method"
  end

  it "handles Constant.foo syntax" do
    obj = Registry.at('#beep')
    expect(obj).not_to be nil
    expect(obj.signature).to eq 'def beep(a, b, c)'
  end

  it "expands attached macros in first DSL method" do
    expect(Registry.at('DSLMethods#foo').docstring).to eq "Returns String for foo"
    expect(Registry.at('DSLMethods#bar').docstring).to eq "Returns Integer for bar"
  end

  it "maintains state on unattached directives" do
    expect(Registry.at('StateParsing.method1')).to be_nil

    %w(method2 method3).each do |meth|
      expect(Registry.at("StateParsing.#{meth}").visibility).to eq(:private)
    end
  end

  it "does not detect implicit macros with invalid method names" do
    undoc_error <<-eof
      ##
      # IMPLICIT METHOD THAT SHOULD
      # NOT BE DETECTED
      dsl_method '/foo/bar'
    eof
  end

  it "warns on unparseable DSL statements" do
    undoc_error "private(*foo(bar))"
  end if HAVE_RIPPER

  # @note Currently unsupported behavior. Overriding a macro on an alias will
  #   not work until macro lookups can be done by caller_method directly.
  # @todo optimize MacroObject lookup to work by caller name, not macro name.
  it "cannot attach a macro on alias of attached macro" do
    expect(Registry.at('AliasTest#main_foo1')).not_to be nil
    expect(Registry.at('AliasTest#alt_foo1')).to be nil
  end if HAVE_RIPPER

  it "searches attached macro's aliases for caller method match" do
    expect(Registry.at('AliasTest#main_foo2')).not_to be nil
    expect(Registry.at('AliasTest#alt_foo2')).to be nil
  end if HAVE_RIPPER

  it "does not create method object if directive data detected in expanded macro" do
    expect(Registry.at('DirectiveMethods#Bar')).to be nil
    expect(Registry.at('DirectiveMethods').mixins).to include P(:Bar)
  end
end
