require_relative '../../spec_helper' require_lib 'reek/smells/utility_function' require_lib 'reek/examiner' require_relative 'smell_detector_shared' RSpec.describe Reek::Smells::UtilityFunction do describe 'a detector' do let(:detector) { build(:smell_detector, smell_type: :UtilityFunction) } it_should_behave_like 'SmellDetector' context 'when a smells is reported' do let(:warning) do src = <<-EOS def simple(arga) arga.b.c end EOS Reek::Examiner.new(src, ['UtilityFunction']).smells[0] end it_should_behave_like 'common fields set correctly' it 'reports the line number of the method' do expect(warning.lines).to eq([1]) end end end context 'with a singleton method' do ['self', 'local_call', '$global'].each do |receiver| it 'ignores the receiver' do src = "def #{receiver}.simple(arga) arga.to_s + arga.to_i end" expect(src).not_to reek_of(:UtilityFunction) end end end context 'Singleton methods' do it 'for classes with `class << self` notation should not report UtilityFunction' do src = 'class C; class << self; def m(a) a.to_s; end; end; end' expect(src).not_to reek_of(:UtilityFunction) end it 'for classes with `self.` notation should not report UtilityFunction' do src = 'class C; def self.m(a) a.to_s; end; end' expect(src).not_to reek_of(:UtilityFunction) end it 'for modules with `class << self` notation should not report UtilityFunction' do src = 'module M; class << self; def self.m(a) a.to_s; end; end; end' expect(src).not_to reek_of(:UtilityFunction) end it 'for modules with `self.` notation should not report UtilityFunction' do src = 'module M; def self.simple(a) a.to_s; end; end' expect(src).not_to reek_of(:UtilityFunction) end context 'by using `module_function`' do it 'should not report UtilityFunction' do src = 'class C; def m(a) a.to_s; end; module_function :m; end' expect(src).not_to reek_of(:UtilityFunction) end it 'should not report UtilityFunction also when using multiple arguments' do src = <<-EOS class C def m1(a) a.to_s; end def m2(a) a.to_s; end module_function :m1, :m2 end EOS expect(src).not_to reek_of(:UtilityFunction) end it 'does not report module functions defined by earlier modifier' do src = <<-EOF module M module_function def simple(a) a.to_s; end end EOF expect(src).not_to reek_of(:UtilityFunction) end it 'reports functions preceded by canceled modifier' do src = <<-EOF module M module_function public def simple(a) a.to_s; end end EOF expect(src).to reek_of(:UtilityFunction) end it 'does not report when module_function is called in separate scope' do src = <<-EOF class C def m(a) a.to_s; end begin module_function :m end end EOF expect(src).not_to reek_of(:UtilityFunction) end it 'does not report when module_function modifier is called in separate scope' do src = <<-EOF class C begin module_function end def m(a) a.to_s; end end EOF expect(src).not_to reek_of(:UtilityFunction) end end end context 'with no calls' do it 'does not report empty method' do expect('def simple(arga) end').not_to reek_of(:UtilityFunction) end it 'does not report literal' do expect('def simple() 3; end').not_to reek_of(:UtilityFunction) end it 'does not report instance variable reference' do expect('def simple() @yellow end').not_to reek_of(:UtilityFunction) end it 'does not report vcall' do expect('def simple() y end').not_to reek_of(:UtilityFunction) end it 'does not report references to self' do expect('def into; self; end').not_to reek_of(:UtilityFunction) end it 'recognises an ivar reference within a block' do expect('def clean(text) text.each { @fred = 3} end').not_to reek_of(:UtilityFunction) end it 'copes with nil superclass' do expect('class Object; def is_maybe?() false end end').not_to reek_of(:UtilityFunction) end end context 'with only one call' do it 'reports a call to a parameter' do expect('def simple(arga) arga.to_s end').to reek_of(:UtilityFunction, name: 'simple') end it 'reports a call to a constant' do expect('def simple(arga) FIELDS[arga] end').to reek_of(:UtilityFunction) end end context 'with two or more calls' do it 'reports two calls' do src = 'def simple(arga) arga.to_s + arga.to_i end' expect(src).to reek_of(:UtilityFunction, name: 'simple') expect(src).not_to reek_of(:FeatureEnvy) end it 'counts a local call in a param initializer' do expect('def simple(arga=local) arga.to_s end').not_to reek_of(:UtilityFunction) end it 'should count usages of self'do expect('def <=>(other) Options[:sort_order].compare(self, other) end'). not_to reek_of(:UtilityFunction) end it 'should count self reference within a dstr' do expect('def as(alias_name); "#{self} as #{alias_name}".to_sym; end'). not_to reek_of(:UtilityFunction) end it 'should count calls to self within a dstr' do expect('def to_sql; "\'#{self.gsub(/\'/, "\'\'")}\'"; end'). not_to reek_of(:UtilityFunction) end it 'should report message chain' do src = 'def simple(arga) arga.b.c end' expect(src).to reek_of(:UtilityFunction, name: 'simple') expect(src).not_to reek_of(:FeatureEnvy) end it 'does not report a method that calls super' do expect('def child(arg) super; arg.to_s; end').not_to reek_of(:UtilityFunction) end it 'does not report a method that calls super with arguments' do expect('def child(arg) super(arg * 2); arg.to_s; end').not_to reek_of(:UtilityFunction) end it 'should recognise a deep call' do src = <<-EOS class Red def deep(text) text.each { |mod| atts = shelve(mod) } end def shelve(val) @shelf << val end end EOS expect(src).not_to reek_of(:UtilityFunction) end end describe 'disabling UtilityFunction via configuration for non-public methods' do let(:configuration) do default_directive = { default_directive: { Reek::Smells::UtilityFunction => { Reek::Smells::UtilityFunction::PUBLIC_METHODS_ONLY_KEY => true } } } Reek::Configuration::AppConfiguration.from_map(default_directive) end context 'public methods' do it 'should still report UtilityFunction' do src = <<-EOS class C def m1(a) a.to_s; end end EOS expect(src).to reek_of(:UtilityFunction, { name: 'C#m1' }, configuration) end end context 'private methods' do it 'should not report UtilityFunction' do src = <<-EOS class C private def m1(a) a.to_s; end end EOS expect(src).not_to reek_of(:UtilityFunction, {}, configuration) end end context 'protected methods' do it 'should not report UtilityFunction' do src = <<-EOS class C protected def m1(a) a.to_s; end end EOS expect(src).not_to reek_of(:UtilityFunction, {}, configuration) end end end end