reek 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +9 -0
  3. data/README.md +54 -5
  4. data/Rakefile +1 -1
  5. data/features/command_line_interface/smell_selection.feature +4 -4
  6. data/features/command_line_interface/smells_count.feature +9 -8
  7. data/features/configuration_files/masking_smells.feature +16 -51
  8. data/features/configuration_files/overrides_defaults.feature +1 -1
  9. data/features/rake_task/rake_task.feature +14 -14
  10. data/features/reports/reports.feature +21 -19
  11. data/features/reports/yaml.feature +35 -63
  12. data/features/samples.feature +55 -54
  13. data/features/support/env.rb +8 -1
  14. data/lib/reek/cli/application.rb +22 -7
  15. data/lib/reek/cli/command.rb +4 -2
  16. data/lib/reek/cli/help_command.rb +1 -1
  17. data/lib/reek/cli/options.rb +3 -3
  18. data/lib/reek/cli/reek_command.rb +4 -8
  19. data/lib/reek/cli/report/formatter.rb +8 -5
  20. data/lib/reek/cli/report/report.rb +6 -5
  21. data/lib/reek/cli/report/strategy.rb +3 -2
  22. data/lib/reek/cli/version_command.rb +1 -1
  23. data/lib/reek/configuration/app_configuration.rb +75 -0
  24. data/lib/reek/configuration/configuration_file_finder.rb +56 -0
  25. data/lib/reek/core/code_context.rb +2 -6
  26. data/lib/reek/core/module_context.rb +4 -0
  27. data/lib/reek/core/smell_repository.rb +5 -3
  28. data/lib/reek/core/sniffer.rb +12 -8
  29. data/lib/reek/examiner.rb +7 -6
  30. data/lib/reek/rake/task.rb +10 -12
  31. data/lib/reek/smell_warning.rb +25 -43
  32. data/lib/reek/smells/attribute.rb +7 -12
  33. data/lib/reek/smells/boolean_parameter.rb +9 -9
  34. data/lib/reek/smells/class_variable.rb +7 -13
  35. data/lib/reek/smells/control_parameter.rb +8 -11
  36. data/lib/reek/smells/data_clump.rb +16 -21
  37. data/lib/reek/smells/duplicate_method_call.rb +11 -18
  38. data/lib/reek/smells/feature_envy.rb +8 -8
  39. data/lib/reek/smells/irresponsible_module.rb +6 -10
  40. data/lib/reek/smells/long_parameter_list.rb +7 -15
  41. data/lib/reek/smells/long_yield_list.rb +13 -15
  42. data/lib/reek/smells/module_initialize.rb +4 -7
  43. data/lib/reek/smells/nested_iterators.rb +6 -13
  44. data/lib/reek/smells/nil_check.rb +9 -7
  45. data/lib/reek/smells/prima_donna_method.rb +5 -7
  46. data/lib/reek/smells/repeated_conditional.rb +19 -15
  47. data/lib/reek/smells/smell_detector.rb +21 -1
  48. data/lib/reek/smells/too_many_instance_variables.rb +9 -16
  49. data/lib/reek/smells/too_many_methods.rb +10 -17
  50. data/lib/reek/smells/too_many_statements.rb +14 -14
  51. data/lib/reek/smells/uncommunicative_method_name.rb +9 -10
  52. data/lib/reek/smells/uncommunicative_module_name.rb +9 -10
  53. data/lib/reek/smells/uncommunicative_parameter_name.rb +9 -9
  54. data/lib/reek/smells/uncommunicative_variable_name.rb +9 -9
  55. data/lib/reek/smells/unused_parameters.rb +8 -20
  56. data/lib/reek/smells/utility_function.rb +12 -10
  57. data/lib/reek/source.rb +0 -1
  58. data/lib/reek/source/code_comment.rb +1 -0
  59. data/lib/reek/source/source_code.rb +3 -13
  60. data/lib/reek/source/source_file.rb +0 -14
  61. data/lib/reek/source/source_repository.rb +7 -0
  62. data/lib/reek/spec/should_reek_of.rb +3 -3
  63. data/lib/reek/spec/should_reek_only_of.rb +2 -2
  64. data/lib/reek/version.rb +1 -1
  65. data/reek.gemspec +4 -2
  66. data/spec/factories/factories.rb +32 -0
  67. data/spec/matchers/smell_of_matcher.rb +3 -2
  68. data/spec/reek/cli/report_spec.rb +2 -1
  69. data/spec/reek/configuration/app_configuration_spec.rb +67 -0
  70. data/spec/reek/configuration/configuration_file_finder_spec.rb +35 -0
  71. data/spec/reek/core/code_context_spec.rb +1 -1
  72. data/spec/reek/core/module_context_spec.rb +5 -1
  73. data/spec/reek/core/smell_configuration_spec.rb +21 -13
  74. data/spec/reek/core/warning_collector_spec.rb +4 -1
  75. data/spec/reek/examiner_spec.rb +19 -1
  76. data/spec/reek/smell_warning_spec.rb +42 -36
  77. data/spec/reek/smells/attribute_spec.rb +6 -2
  78. data/spec/reek/smells/boolean_parameter_spec.rb +11 -12
  79. data/spec/reek/smells/class_variable_spec.rb +16 -6
  80. data/spec/reek/smells/control_parameter_spec.rb +17 -19
  81. data/spec/reek/smells/data_clump_spec.rb +25 -15
  82. data/spec/reek/smells/duplicate_method_call_spec.rb +18 -12
  83. data/spec/reek/smells/feature_envy_spec.rb +29 -10
  84. data/spec/reek/smells/irresponsible_module_spec.rb +7 -7
  85. data/spec/reek/smells/long_parameter_list_spec.rb +16 -10
  86. data/spec/reek/smells/long_yield_list_spec.rb +2 -2
  87. data/spec/reek/smells/module_initialize_spec.rb +26 -0
  88. data/spec/reek/smells/nested_iterators_spec.rb +21 -10
  89. data/spec/reek/smells/nil_check_spec.rb +0 -2
  90. data/spec/reek/smells/prima_donna_method_spec.rb +3 -3
  91. data/spec/reek/smells/repeated_conditional_spec.rb +0 -26
  92. data/spec/reek/smells/smell_detector_shared.rb +4 -4
  93. data/spec/reek/smells/too_many_instance_variables_spec.rb +3 -3
  94. data/spec/reek/smells/too_many_methods_spec.rb +16 -11
  95. data/spec/reek/smells/too_many_statements_spec.rb +55 -18
  96. data/spec/reek/smells/uncommunicative_method_name_spec.rb +3 -2
  97. data/spec/reek/smells/uncommunicative_module_name_spec.rb +5 -5
  98. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +4 -4
  99. data/spec/reek/smells/uncommunicative_variable_name_spec.rb +28 -21
  100. data/spec/reek/smells/unused_parameters_spec.rb +3 -5
  101. data/spec/reek/smells/utility_function_spec.rb +2 -1
  102. data/spec/reek/source/code_comment_spec.rb +7 -2
  103. data/spec/reek/source/reference_collector_spec.rb +0 -1
  104. data/spec/reek/source/sexp_extensions_spec.rb +0 -15
  105. data/spec/reek/source/source_code_spec.rb +13 -1
  106. data/spec/reek/spec/should_reek_only_of_spec.rb +22 -10
  107. data/spec/reek/spec/should_reek_spec.rb +6 -2
  108. data/spec/samples/minimal_smelly_and_masked/config.reek +7 -0
  109. data/spec/samples/minimal_smelly_and_masked/minimal_dirty.rb +4 -0
  110. data/spec/samples/simple_configuration.reek +5 -0
  111. data/spec/samples/standard_smelly/dirty.rb +8 -0
  112. data/spec/samples/standard_smelly/minimal_dirty.rb +4 -0
  113. data/spec/spec_helper.rb +20 -0
  114. data/tasks/develop.rake +1 -1
  115. data/tasks/rubocop.rake +5 -0
  116. metadata +41 -6
  117. data/lib/reek/config_file_exception.rb +0 -7
  118. data/lib/reek/smell_description.rb +0 -26
  119. data/lib/reek/source/config_file.rb +0 -88
  120. data/spec/reek/smell_description_spec.rb +0 -43
@@ -9,8 +9,8 @@ module Reek
9
9
  #
10
10
  class ReekCommand < Command
11
11
  def execute(app)
12
- @parser.sources.each do |source|
13
- reporter.add_examiner(Examiner.new(source, config_files, smell_names))
12
+ @options.sources.each do |source|
13
+ reporter.add_examiner Examiner.new(source, smell_names)
14
14
  end
15
15
  reporter.smells? ? app.report_smells : app.report_success
16
16
  reporter.show
@@ -19,15 +19,11 @@ module Reek
19
19
  private
20
20
 
21
21
  def reporter
22
- @reporter ||= @parser.reporter
22
+ @reporter ||= @options.reporter
23
23
  end
24
24
 
25
25
  def smell_names
26
- @smell_names ||= @parser.smells_to_detect
27
- end
28
-
29
- def config_files
30
- @config_files ||= @parser.config_files
26
+ @smell_names ||= @options.smells_to_detect
31
27
  end
32
28
  end
33
29
  end
@@ -10,7 +10,8 @@ module Reek
10
10
 
11
11
  def self.header(examiner)
12
12
  count = examiner.smells_count
13
- result = Rainbow("#{examiner.description} -- ").cyan + Rainbow("#{count} warning").yellow
13
+ result = Rainbow("#{examiner.description} -- ").cyan +
14
+ Rainbow("#{count} warning").yellow
14
15
  result += Rainbow('s').yellow unless count == 1
15
16
  result
16
17
  end
@@ -22,11 +23,12 @@ module Reek
22
23
  module_function
23
24
 
24
25
  def format(warning)
25
- "#{WarningFormatterWithLineNumbers.format(warning)} [#{explanatory_link(warning)}]"
26
+ "#{WarningFormatterWithLineNumbers.format(warning)} " \
27
+ "[#{explanatory_link(warning)}]"
26
28
  end
27
29
 
28
30
  def explanatory_link(warning)
29
- "#{BASE_URL_FOR_HELP_LINK}#{class_name_to_param(warning.subclass)}"
31
+ "#{BASE_URL_FOR_HELP_LINK}#{class_name_to_param(warning.smell_type)}"
30
32
  end
31
33
 
32
34
  def class_name_to_param(name)
@@ -36,7 +38,7 @@ module Reek
36
38
 
37
39
  module SimpleWarningFormatter
38
40
  def self.format(warning)
39
- "#{warning.context} #{warning.message} (#{warning.subclass})"
41
+ "#{warning.context} #{warning.message} (#{warning.smell_type})"
40
42
  end
41
43
  end
42
44
 
@@ -48,7 +50,8 @@ module Reek
48
50
 
49
51
  module SingleLineWarningFormatter
50
52
  def self.format(warning)
51
- "#{warning.source}:#{warning.lines.first}: #{SimpleWarningFormatter.format(warning)}"
53
+ "#{warning.source}:#{warning.lines.first}: " \
54
+ "#{SimpleWarningFormatter.format(warning)}"
52
55
  end
53
56
  end
54
57
  end
@@ -58,12 +58,13 @@ module Reek
58
58
  end
59
59
 
60
60
  def sort_examiners
61
- @examiners.sort! { |first, second| second.smells_count <=> first.smells_count } if @sort_by_issue_count
61
+ @examiners.sort_by!(&:smells_count).reverse! if @sort_by_issue_count
62
62
  end
63
63
 
64
64
  def total_smell_count_message
65
65
  colour = smells? ? WARNINGS_COLOR : NO_WARNINGS_COLOR
66
- Rainbow("#{@total_smell_count} total warning#{'s' unless @total_smell_count == 1 }\n").color(colour)
66
+ s = @total_smell_count == 1 ? '' : 's'
67
+ Rainbow("#{@total_smell_count} total warning#{s}\n").color(colour)
67
68
  end
68
69
  end
69
70
 
@@ -92,11 +93,11 @@ module Reek
92
93
 
93
94
  require 'erb'
94
95
 
95
- TEMPLATE = File.read(File.expand_path('../../../../../assets/html_output.html.erb', __FILE__))
96
-
97
96
  def show
97
+ path = File.expand_path('../../../../../assets/html_output.html.erb',
98
+ __FILE__)
98
99
  File.open('reek.html', 'w+') do |file|
99
- file.puts ERB.new(TEMPLATE).result(binding)
100
+ file.puts ERB.new(File.read(path)).result(binding)
100
101
  end
101
102
  print("Html file saved\n")
102
103
  end
@@ -18,7 +18,8 @@ module Reek
18
18
  def summarize_single_examiner(examiner)
19
19
  result = report_formatter.header examiner
20
20
  if examiner.smelly?
21
- formatted_list = report_formatter.format_list examiner.smells, warning_formatter
21
+ formatted_list = report_formatter.format_list(examiner.smells,
22
+ warning_formatter)
22
23
  result += ":\n#{formatted_list}"
23
24
  end
24
25
  result
@@ -54,7 +55,7 @@ module Reek
54
55
  class Normal < Base
55
56
  def gather_results
56
57
  examiners.each_with_object([]) { |examiner, smells| smells << examiner.smells }.
57
- flatten
58
+ flatten
58
59
  end
59
60
  end
60
61
  end
@@ -8,7 +8,7 @@ module Reek
8
8
  #
9
9
  class VersionCommand < Command
10
10
  def execute(view)
11
- view.output("#{@parser.program_name} #{Reek::VERSION}\n")
11
+ view.output("#{@options.program_name} #{Reek::VERSION}\n")
12
12
  view.report_success
13
13
  end
14
14
  end
@@ -0,0 +1,75 @@
1
+ require_relative './configuration_file_finder'
2
+
3
+ module Reek
4
+ module Configuration
5
+ class ConfigFileException < StandardError; end
6
+ #
7
+ # Reeks singleton configuration instance.
8
+ #
9
+ module AppConfiguration
10
+ @configuration = {}
11
+ @has_been_initialized = false
12
+
13
+ class << self
14
+ attr_reader :configuration
15
+
16
+ def initialize_with(application)
17
+ @has_been_initialized = true
18
+ configuration_file_path = ConfigurationFileFinder.find(application)
19
+ return unless configuration_file_path
20
+ load_from_file configuration_file_path
21
+ end
22
+
23
+ def configure_smell_repository(smell_repository)
24
+ # Let users call this method directly without having initialized AppConfiguration before
25
+ # and if they do, initialize it without application context
26
+ initialize_with(nil) unless @has_been_initialized
27
+ @configuration.each do |klass_name, config|
28
+ klass = load_smell_type(klass_name)
29
+ smell_repository.configure(klass, config) if klass
30
+ end
31
+ end
32
+
33
+ def load_from_file(path)
34
+ if File.size(path) == 0
35
+ report_problem('Empty file', path)
36
+ return
37
+ end
38
+
39
+ begin
40
+ @configuration = YAML.load_file(path) || {}
41
+ rescue => error
42
+ raise_error(error.to_s, path)
43
+ end
44
+
45
+ raise_error('Not a hash', path) unless @configuration.is_a? Hash
46
+ end
47
+
48
+ def reset
49
+ @configuration.clear
50
+ end
51
+
52
+ private
53
+
54
+ def load_smell_type(name)
55
+ Reek::Smells.const_get(name)
56
+ rescue NameError
57
+ report_problem("\"#{name}\" is not a code smell")
58
+ nil
59
+ end
60
+
61
+ def report_problem(reason, path)
62
+ $stderr.puts "Warning: #{message(reason, path)}"
63
+ end
64
+
65
+ def raise_error(reason, path)
66
+ raise ConfigFileException, message(reason, path)
67
+ end
68
+
69
+ def message(reason, path)
70
+ "Invalid configuration file \"#{File.basename(path)}\" -- #{reason}"
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,56 @@
1
+ require 'pathname'
2
+
3
+ module Reek
4
+ module Configuration
5
+ #
6
+ # ConfigurationFileFinder is responsible for finding reeks configuration.
7
+ #
8
+ # There are 3 ways of passing `reek` a configuration file:
9
+ # 1. Using the cli "-c" switch (see "Command line interface" above)
10
+ # 2. Having a file ending with .reek either in your current working directory or in a parent
11
+ # directory
12
+ # 3. Having a file ending with .reek in your HOME directory
13
+ #
14
+ # The order in which ConfigurationFileFinder tries to find such a configuration file is exactly
15
+ # like above.
16
+ module ConfigurationFileFinder
17
+ class << self
18
+ def find(application)
19
+ configuration_by_cli(application) ||
20
+ configuration_in_file_system ||
21
+ configuration_in_home_directory
22
+ end
23
+
24
+ private
25
+
26
+ def configuration_by_cli(application)
27
+ return unless application # Return gracefully allowing calls without app context
28
+ config_file_option = application.options.config_file
29
+ return unless config_file_option
30
+ path_name = Pathname.new config_file_option
31
+ raise ArgumentError, "Config file #{path_name} doesn't exist" unless path_name.exist?
32
+ path_name
33
+ end
34
+
35
+ def configuration_in_file_system
36
+ detect_or_traverse_up Pathname.pwd
37
+ end
38
+
39
+ def configuration_in_home_directory
40
+ detect_configuration_in_directory Pathname.new(Dir.home)
41
+ end
42
+
43
+ def detect_or_traverse_up(directory)
44
+ file = detect_configuration_in_directory(directory)
45
+ return file unless file.nil?
46
+ return if directory.root?
47
+ detect_or_traverse_up directory.parent
48
+ end
49
+
50
+ def detect_configuration_in_directory(directory)
51
+ Pathname.glob(directory.join('*.reek')).detect(&:file?)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -19,12 +19,8 @@ module Reek
19
19
  @exp.name
20
20
  end
21
21
 
22
- def node_instance_methods
23
- local_nodes(:def)
24
- end
25
-
26
22
  def local_nodes(type, &blk)
27
- each_node(type, [:class, :module], &blk)
23
+ each_node(type, [:casgn, :class, :module], &blk)
28
24
  end
29
25
 
30
26
  def each_node(type, ignoring, &blk)
@@ -55,7 +51,7 @@ module Reek
55
51
 
56
52
  def config_for(detector_class)
57
53
  outer_config_for(detector_class).merge(
58
- config[detector_class.smell_class_name] || {})
54
+ config[detector_class.smell_type] || {})
59
55
  end
60
56
 
61
57
  private
@@ -11,6 +11,10 @@ module Reek
11
11
  super(outer, exp)
12
12
  @name = Source::SexpFormatter.format(exp.children.first)
13
13
  end
14
+
15
+ def node_instance_methods
16
+ local_nodes(:def)
17
+ end
14
18
  end
15
19
  end
16
20
  end
@@ -6,7 +6,9 @@ module Reek
6
6
  # Contains all the existing smells and exposes operations on them.
7
7
  #
8
8
  class SmellRepository
9
- def self.smell_classes
9
+ attr_reader :detectors
10
+
11
+ def self.smell_types
10
12
  # SMELL: Duplication -- these should be loaded by listing the files
11
13
  [
12
14
  Smells::Attribute,
@@ -36,10 +38,10 @@ module Reek
36
38
  ]
37
39
  end
38
40
 
39
- def initialize(source_description, smell_classes = SmellRepository.smell_classes)
41
+ def initialize(source_description, smell_types = SmellRepository.smell_types)
40
42
  @typed_detectors = nil
41
43
  @detectors = {}
42
- smell_classes.each do |klass|
44
+ smell_types.each do |klass|
43
45
  @detectors[klass] = klass.new(source_description)
44
46
  end
45
47
  end
@@ -1,6 +1,6 @@
1
1
  require 'reek/core/code_parser'
2
2
  require 'reek/core/smell_repository'
3
- require 'reek/source/config_file'
3
+ require 'reek/configuration/app_configuration'
4
4
 
5
5
  module Reek
6
6
  module Core
@@ -8,24 +8,28 @@ module Reek
8
8
  # Configures all available smell detectors and applies them to a source.
9
9
  #
10
10
  class Sniffer
11
- def initialize(src,
12
- extra_config_files = [],
13
- smell_repository = Core::SmellRepository.new(src.desc))
11
+ def initialize(source, # Either Source::SourceFile or Source::SourceCode
12
+ smell_repository = Core::SmellRepository.new(source.desc))
14
13
  @smell_repository = smell_repository
15
- @source = src
14
+ @source = source
16
15
 
17
- config_files = extra_config_files + @source.relevant_config_files
18
- config_files.each { |cf| Reek::Source::ConfigFile.new(cf).configure(@smell_repository) }
16
+ Configuration::AppConfiguration.configure_smell_repository(@smell_repository)
19
17
  end
20
18
 
21
19
  def report_on(listener)
22
- CodeParser.new(@smell_repository).process(@source.syntax_tree)
20
+ CodeParser.new(@smell_repository).process(syntax_tree) if syntax_tree
23
21
  @smell_repository.report_on(listener)
24
22
  end
25
23
 
26
24
  def examine(scope, node_type)
27
25
  @smell_repository.examine scope, node_type
28
26
  end
27
+
28
+ private
29
+
30
+ def syntax_tree
31
+ @syntax_tree ||= @source.syntax_tree
32
+ end
29
33
  end
30
34
  end
31
35
  end
@@ -25,19 +25,20 @@ module Reek
25
25
  # and if it is an Array, it is assumed to be a list of file paths,
26
26
  # each of which is opened and parsed for source code.
27
27
  #
28
- def initialize(source, config_files = [], smell_names = [])
28
+ def initialize(source, smell_types_to_filter_by = [])
29
29
  sources = Source::SourceRepository.parse(source)
30
30
  @description = sources.description
31
31
  @collector = Core::WarningCollector.new
32
32
 
33
- smell_classes = Core::SmellRepository.smell_classes
34
- if smell_names.any?
35
- smell_classes.select! { |klass| smell_names.include? klass.smell_class_name }
33
+ smell_types = Core::SmellRepository.smell_types
34
+
35
+ if smell_types_to_filter_by.any?
36
+ smell_types.select! { |klass| smell_types_to_filter_by.include? klass.smell_type }
36
37
  end
37
38
 
38
39
  sources.each do |src|
39
- repository = Core::SmellRepository.new(src.desc, smell_classes)
40
- Core::Sniffer.new(src, config_files, repository).report_on(@collector)
40
+ repository = Core::SmellRepository.new(src.desc, smell_types)
41
+ Core::Sniffer.new(src, repository).report_on(@collector)
41
42
  end
42
43
  end
43
44
 
@@ -42,7 +42,7 @@ module Reek
42
42
  # Glob pattern to match config files.
43
43
  # Setting the REEK_CFG environment variable overrides this.
44
44
  # Defaults to 'config/**/*.reek'.
45
- attr_accessor :config_files
45
+ attr_accessor :config_file
46
46
 
47
47
  # Glob pattern to match source files.
48
48
  # Setting the REEK_SRC environment variable overrides this.
@@ -69,7 +69,7 @@ module Reek
69
69
  def initialize(name = :reek)
70
70
  @name = name
71
71
  @libs = [File.expand_path(File.dirname(__FILE__) + '/../../../lib')]
72
- @config_files = nil
72
+ @config_file = nil
73
73
  @source_files = nil
74
74
  @ruby_opts = []
75
75
  @reek_opts = ''
@@ -77,7 +77,7 @@ module Reek
77
77
  @sort = nil
78
78
 
79
79
  yield self if block_given?
80
- @config_files ||= 'config/**/*.reek'
80
+ @config_file ||= Pathname.glob('config/**/*.reek').detect(&:file?)
81
81
  @source_files ||= 'lib/**/*.rb'
82
82
  define
83
83
  end
@@ -103,17 +103,15 @@ module Reek
103
103
 
104
104
  def cmd_words
105
105
  [Task.ruby_exe] +
106
- ruby_options +
107
- [%(reek)] +
108
- [sort_option] +
109
- config_file_list.map { |fn| ['-c', %("#{fn}")] }.flatten +
110
- source_file_list.map { |fn| %("#{fn}") }
106
+ ruby_options +
107
+ [%(reek)] +
108
+ [sort_option] +
109
+ ['-c', config_file] +
110
+ source_file_list.map { |fn| %("#{fn}") }
111
111
  end
112
112
 
113
- def config_file_list
114
- files = ENV['REEK_CFG'] || @config_files
115
- return [] unless files
116
- FileList[files]
113
+ def config_file
114
+ ENV['REEK_CFG'] || @config_file
117
115
  end
118
116
 
119
117
  def ruby_options