require_relative '../../spec_helper'
require_lib 'reek/smells/uncommunicative_variable_name'
require_relative 'smell_detector_shared'

RSpec.describe Reek::Smells::UncommunicativeVariableName do
  let(:detector) do
    build(:smell_detector, smell_type: :UncommunicativeVariableName)
  end

  it_should_behave_like 'SmellDetector'

  context 'field name' do
    it 'does not report use of one-letter fieldname' do
      src = 'class Thing; def simple(fred) @x end end'
      expect(src).not_to reek_of(:UncommunicativeVariableName)
    end
    it 'reports one-letter fieldname in assignment' do
      src = 'class Thing; def simple(fred) @x = fred end end'
      expect(src).to reek_of(:UncommunicativeVariableName, name: '@x')
    end
  end

  context 'local variable name' do
    it 'does not report one-word variable name' do
      expect('def help(fred) simple = jim(45) end').
        not_to reek_of(:UncommunicativeVariableName)
    end

    it 'does not report single underscore as a variable name' do
      expect('def help(fred) _ = jim(45) end').not_to reek_of(:UncommunicativeVariableName)
    end

    it 'reports one-letter variable name' do
      src = 'def simple(fred) x = jim(45) end'
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'x')
    end

    it 'reports name of the form "x2"' do
      src = 'def simple(fred) x2 = jim(45) end'
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'x2')
    end

    it 'reports long name ending in a number' do
      bad_var = 'var123'
      src = "def simple(fred) #{bad_var} = jim(45) end"
      expect(src).to reek_of(:UncommunicativeVariableName,
                             name: bad_var)
    end

    it 'reports variable name only once' do
      src = 'def simple(fred) x = jim(45); x = y end'
      syntax_tree = Reek::Source::SourceCode.from(src).syntax_tree
      ctx = Reek::Context::CodeContext.new(nil, syntax_tree)
      smells = detector.inspect(ctx)
      expect(smells.length).to eq(1)
      expect(smells[0].smell_type).to eq(described_class.smell_type)
      expect(smells[0].parameters[:name]).to eq('x')
      expect(smells[0].lines).to eq([1, 1])
    end

    it 'reports a bad name inside a block' do
      src = 'def clean(text) text.each { q2 = 3 } end'
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'q2')
    end

    it 'reports variable name outside any method' do
      expect('class Simple; x = jim(45); end').to reek_of(:UncommunicativeVariableName,
                                                          name: 'x')
    end
  end

  context 'block parameter name' do
    it 'reports deep block parameter' do
      src = <<-EOS
        def bad
          unless @mod then
            @sig.each { |x| x.to_s }
          end
        end
      EOS
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'x')
    end

    it 'reports all relevant block parameters' do
      src = <<-EOS
        def bad
          @foo.map { |x, y| x + y }
        end
      EOS
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'x')
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'y')
    end

    it 'reports block parameters used outside of methods' do
      src = <<-EOS
      class Foo
        @foo.map { |x| x * 2 }
      end
      EOS
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'x')
    end

    it 'reports splatted block parameters correctly' do
      src = <<-EOS
        def bad
          @foo.map { |*y| y << 1 }
        end
      EOS
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'y')
    end

    it 'reports nested block parameters' do
      src = <<-EOS
        def bad
          @foo.map { |(x, y)| x + y }
        end
      EOS
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'x')
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'y')
    end

    it 'reports splatted nested block parameters' do
      src = <<-EOS
        def bad
          @foo.map { |(x, *y)| x + y }
        end
      EOS
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'x')
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'y')
    end

    it 'reports deeply nested block parameters' do
      src = <<-EOS
        def bad
          @foo.map { |(x, (y, z))| x + y + z }
        end
      EOS
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'x')
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'y')
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'z')
    end

    it 'reports shadowed block parameters' do
      src = <<-EOS
        def bad
          @foo.map { |x; y| y = x * 2 }
        end
      EOS
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'x')
      expect(src).to reek_of(:UncommunicativeVariableName, name: 'y')
    end
  end

  context 'when a smell is reported' do
    let(:warning) do
      src = <<-EOS
        def bad
          unless @mod then
             x2 = xy.to_s
             x2
             x2 = 56
          end
        end
      EOS
      syntax_tree = Reek::Source::SourceCode.from(src).syntax_tree
      ctx = Reek::Context::CodeContext.new(nil, syntax_tree)
      detector.inspect(ctx).first
    end

    it_should_behave_like 'common fields set correctly'

    it 'reports the correct values' do
      expect(warning.parameters[:name]).to eq('x2')
      expect(warning.lines).to eq([3, 5])
    end
  end

  context 'when a smell is reported in a singleton method' do
    let(:warning) do
      src = 'def self.bad() x2 = 4; end'
      syntax_tree = Reek::Source::SourceCode.from(src).syntax_tree
      ctx = Reek::Context::CodeContext.new(nil, syntax_tree)
      detector.inspect(ctx).first
    end

    it_should_behave_like 'common fields set correctly'

    it 'reports the fq context' do
      expect(warning.context).to eq('self.bad')
    end
  end
end
