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

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

  it "does not parse constants inside methods" do
    expect(Registry.at("A::B::SOMECONSTANT").source).to eq "SOMECONSTANT= \"hello\""
  end

  it "only parses valid constants" do
    expect(Registry.at("A::B::notaconstant")).to be nil
  end

  it "maintains newlines" do
    expect(Registry.at("A::B::MYCONSTANT").value.delete("\r")).to eq "A +\nB +\nC +\nD"
  end

  it "turns Const = Struct.new(:sym) into class Const with attr :sym" do
    obj = Registry.at("MyClass")
    expect(obj).to be_kind_of(CodeObjects::ClassObject)
    attrs = obj.attributes[:instance]
    [:a, :b, :c].each do |key|
      expect(attrs).to have_key(key)
      expect(attrs[key][:read]).not_to be nil
      expect(attrs[key][:write]).not_to be nil
    end
  end

  it 'documents block for Struct.new if present' do
    obj = Registry.at("MyStructWithConstant")
    expect(obj).to be_kind_of(CodeObjects::ClassObject)
    expect(obj.constants[0].docstring).to eq 'A constant.'
    expect(obj.constants[0].name).to eq :CONSTANT
    expect(obj.constants[0].value).to eq "42"
    expect(obj.constants[1].docstring).to eq 'Special constant (empty symbol)'
    expect(obj.constants[1].name).to eq :EMPTY
    expect(obj.constants[1].value).to eq ":''"
  end

  it "turns Const = Struct.new('Name', :sym) into class Const with attr :sym" do
    obj = Registry.at("NotMyClass")
    expect(obj).to be_kind_of(CodeObjects::ClassObject)
    attrs = obj.attributes[:instance]
    [:b, :c].each do |key|
      expect(attrs).to have_key(key)
      expect(attrs[key][:read]).not_to be nil
      expect(attrs[key][:write]).not_to be nil
    end

    expect(Registry.at("NotMyClass2")).to be nil
  end

  it "turns Const = Struct.new into empty struct" do
    obj = Registry.at("MyEmptyStruct")
    expect(obj).not_to be nil
    expect(obj.attributes[:instance]).to be_empty
  end

  it "turns A::Const = Struct.new(:sym) into class A::Const with attr :sym" do
    obj = Registry.at("A::NestedCompactStruct")
    expect(obj).to be_kind_of(CodeObjects::ClassObject)
    expect(obj.superclass).to eq P(:Struct)
    attrs = obj.attributes[:instance]
    [:b, :c].each do |key|
      expect(attrs).to have_key(key)
      expect(attrs[key][:read]).not_to be nil
      expect(attrs[key][:write]).not_to be nil
    end
  end

  it "maintains docstrings on structs defined via constants" do
    obj = Registry.at("DocstringStruct")
    expect(obj).not_to be nil
    expect(obj.docstring).to eq "A crazy struct."
    expect(obj.attributes[:instance]).not_to be_empty
    a1 = Registry.at("DocstringStruct#bar")
    a2 = Registry.at("DocstringStruct#baz")
    expect(a1.docstring).to eq "An attr"
    expect(a1.tag(:return).types).to eq ["String"]
    expect(a2.docstring).to eq "Another attr"
    expect(a2.tag(:return).types).to eq ["Number"]
    a3 = Registry.at("DocstringStruct#new_syntax")
    expect(a3.docstring).to eq "Attribute defined with the new syntax"
    expect(a3.tag(:return).types).to eq ["Symbol"]
  end

  it "turns Const = Data.define(:sym) into class Const with attr reader :sym" do
    obj = Registry.at("MyData")
    expect(obj).to be_kind_of(CodeObjects::ClassObject)
    expect(obj.superclass).to eq P(:Data)
    attrs = obj.attributes[:instance]
    [:a, :b, :c].each do |key|
      expect(attrs).to have_key(key)
      expect(attrs[key][:read]).not_to be nil
      expect(attrs[key][:write]).to be nil
    end
  end

  it "turns Const = Data.define into empty data class" do
    obj = Registry.at("MyEmptyData")
    expect(obj).to be_kind_of(CodeObjects::ClassObject)
    expect(obj.superclass).to eq P(:Data)
    expect(obj.attributes[:instance]).to be_empty
  end

  it "turns A::Const = Data.define(:sym) into class A::Const with attr reader :sym" do
    obj = Registry.at("A::NestedCompactData")
    expect(obj).to be_kind_of(CodeObjects::ClassObject)
    expect(obj.superclass).to eq P(:Data)
    attrs = obj.attributes[:instance]
    [:b, :c].each do |key|
      expect(attrs).to have_key(key)
      expect(attrs[key][:read]).not_to be nil
      expect(attrs[key][:write]).to be nil
    end
  end

  it "documents block for Data.define if present" do
    obj = Registry.at("MyDataWithMethods")
    expect(obj).to be_kind_of(CodeObjects::ClassObject)
    expect(obj.superclass).to eq P(:Data)
    attrs = obj.attributes[:instance]
    [:c, :d].each do |key|
      expect(attrs).to have_key(key)
      expect(attrs[key][:read]).not_to be nil
      expect(attrs[key][:write]).to be nil
    end
    expect(obj.meths).to include(P("MyDataWithMethods#foo"))
  end

  it "raises undocumentable error in 1.9 parser for Struct.new assignment to non-const" do
    undoc_error "nonconst = Struct.new"
  end unless LEGACY_PARSER

  %w(module class).each do |type|
    it "does not allow #{type} to be redefined as constant" do
      undoc_error <<-eof
        #{type} Foo; end
        Foo = "value"
      eof
    end
  end unless LEGACY_PARSER

  it "allows constant to have same name as constant in parent namespace" do
    YARD.parse_string <<-eof
      module A
        class C; end
        module B; C = 1 end
      end
    eof
    expect(log.io.string).to eq ""
    expect(Registry.at('A::B::C').type).to eq :constant
  end

  it "preserves @!parse attr_reader documentation for Data.define members" do
    Registry.clear
    YARD.parse_string <<-eof
      class MyReturn; end
      # @!parse
      #   class ParsedData < Data
      #     # First attr.
      #     # @return [Integer]
      #     attr_reader :x
      #     # Second attr.
      #     # @return [MyReturn]
      #     attr_reader :y
      #   end
      ParsedData = Data.define :x, :y
    eof
    obj = Registry.at("ParsedData")
    expect(obj).to be_kind_of(CodeObjects::ClassObject)
    expect(obj.superclass).to eq P(:Data)
    x = Registry.at("ParsedData#x")
    expect(x.docstring).to eq "First attr."
    expect(x.tag(:return).types).to eq ["Integer"]
    y = Registry.at("ParsedData#y")
    expect(y.docstring).to eq "Second attr."
    expect(y.tag(:return).types).to eq ["MyReturn"]
  end if HAVE_RIPPER

  it "preserves @!parse @!attribute documentation for Data.define members" do
    Registry.clear
    YARD.parse_string <<-eof
      class MyReturn; end
      # @!parse
      #   # @!attribute [r] x
      #   #   First attr.
      #   #   @return [Integer]
      #   # @!attribute [r] y
      #   #   Second attr.
      #   #   @return [MyReturn]
      #   class ParsedAttrData < Data; end
      ParsedAttrData = Data.define :x, :y
    eof
    obj = Registry.at("ParsedAttrData")
    expect(obj).to be_kind_of(CodeObjects::ClassObject)
    expect(obj.superclass).to eq P(:Data)
    x = Registry.at("ParsedAttrData#x")
    expect(x.docstring).to eq "First attr."
    expect(x.tag(:return).types).to eq ["Integer"]
    y = Registry.at("ParsedAttrData#y")
    expect(y.docstring).to eq "Second attr."
    expect(y.tag(:return).types).to eq ["MyReturn"]
  end if HAVE_RIPPER

  it "detects compound constant names" do
    YARD.parse_string <<-eof
      module A
        class AA; end
        AA::B = true
      end
      A::AA::C = true
    eof

    expect(Registry.at('A::AA::B').type).to eq :constant
    expect(Registry.at('A::AA::C').type).to eq :constant
  end if HAVE_RIPPER
end
