reek 1.6.6 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +6 -9
  3. data/features/command_line_interface/options.feature +20 -16
  4. data/features/command_line_interface/stdin.feature +1 -1
  5. data/features/rake_task/rake_task.feature +0 -12
  6. data/features/reports/reports.feature +63 -23
  7. data/features/reports/yaml.feature +3 -3
  8. data/features/samples.feature +3 -3
  9. data/lib/reek/cli/application.rb +5 -5
  10. data/lib/reek/cli/input.rb +1 -1
  11. data/lib/reek/cli/option_interpreter.rb +77 -0
  12. data/lib/reek/cli/options.rb +89 -82
  13. data/lib/reek/cli/report/formatter.rb +33 -24
  14. data/lib/reek/cli/report/heading_formatter.rb +45 -0
  15. data/lib/reek/cli/report/location_formatter.rb +23 -0
  16. data/lib/reek/cli/report/report.rb +32 -17
  17. data/lib/reek/configuration/app_configuration.rb +2 -2
  18. data/lib/reek/configuration/configuration_file_finder.rb +10 -10
  19. data/lib/reek/core/smell_repository.rb +3 -28
  20. data/lib/reek/rake/task.rb +35 -76
  21. data/lib/reek/smell_warning.rb +31 -16
  22. data/lib/reek/smells/nested_iterators.rb +1 -1
  23. data/lib/reek/smells/smell_detector.rb +9 -0
  24. data/lib/reek/smells/utility_function.rb +2 -1
  25. data/lib/reek/spec/should_reek.rb +0 -3
  26. data/lib/reek/spec/should_reek_of.rb +61 -12
  27. data/lib/reek/spec/should_reek_only_of.rb +12 -10
  28. data/lib/reek/version.rb +1 -1
  29. data/reek.gemspec +2 -2
  30. data/spec/factories/factories.rb +2 -5
  31. data/spec/reek/cli/html_report_spec.rb +28 -0
  32. data/spec/reek/cli/option_interperter_spec.rb +14 -0
  33. data/spec/reek/cli/text_report_spec.rb +95 -0
  34. data/spec/reek/cli/yaml_report_spec.rb +23 -0
  35. data/spec/reek/configuration/configuration_file_finder_spec.rb +5 -6
  36. data/spec/reek/core/module_context_spec.rb +1 -1
  37. data/spec/reek/core/smell_repository_spec.rb +17 -0
  38. data/spec/reek/smell_warning_spec.rb +9 -11
  39. data/spec/reek/smells/boolean_parameter_spec.rb +11 -11
  40. data/spec/reek/smells/control_parameter_spec.rb +40 -40
  41. data/spec/reek/smells/data_clump_spec.rb +17 -17
  42. data/spec/reek/smells/duplicate_method_call_spec.rb +56 -33
  43. data/spec/reek/smells/feature_envy_spec.rb +44 -40
  44. data/spec/reek/smells/irresponsible_module_spec.rb +1 -1
  45. data/spec/reek/smells/long_parameter_list_spec.rb +12 -12
  46. data/spec/reek/smells/long_yield_list_spec.rb +4 -4
  47. data/spec/reek/smells/module_initialize_spec.rb +3 -3
  48. data/spec/reek/smells/nested_iterators_spec.rb +71 -52
  49. data/spec/reek/smells/nil_check_spec.rb +6 -6
  50. data/spec/reek/smells/prima_donna_method_spec.rb +2 -2
  51. data/spec/reek/smells/too_many_statements_spec.rb +34 -34
  52. data/spec/reek/smells/uncommunicative_method_name_spec.rb +1 -1
  53. data/spec/reek/smells/uncommunicative_module_name_spec.rb +7 -3
  54. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +12 -12
  55. data/spec/reek/smells/uncommunicative_variable_name_spec.rb +28 -38
  56. data/spec/reek/smells/unused_parameters_spec.rb +16 -17
  57. data/spec/reek/smells/utility_function_spec.rb +21 -8
  58. data/spec/reek/spec/should_reek_of_spec.rb +18 -5
  59. data/spec/reek/spec/should_reek_only_of_spec.rb +7 -1
  60. data/spec/spec_helper.rb +22 -14
  61. metadata +15 -20
  62. data/lib/reek/cli/help_command.rb +0 -15
  63. data/lib/reek/cli/report/strategy.rb +0 -64
  64. data/lib/reek/cli/version_command.rb +0 -16
  65. data/spec/matchers/smell_of_matcher.rb +0 -95
  66. data/spec/reek/cli/help_command_spec.rb +0 -25
  67. data/spec/reek/cli/report_spec.rb +0 -132
  68. data/spec/reek/cli/version_command_spec.rb +0 -31
@@ -1,8 +1,14 @@
1
+ require 'reek/cli/report/location_formatter'
2
+
1
3
  module Reek
2
4
  module Cli
3
5
  module Report
6
+ #
7
+ # Formatter handling the formatting of the report at large. Formatting
8
+ # the individual warnings is handled by the warning formatter passed in.
9
+ #
4
10
  module Formatter
5
- def self.format_list(warnings, formatter = SimpleWarningFormatter)
11
+ def self.format_list(warnings, formatter = SimpleWarningFormatter.new)
6
12
  warnings.map do |warning|
7
13
  " #{formatter.format warning}"
8
14
  end.join("\n")
@@ -17,41 +23,44 @@ module Reek
17
23
  end
18
24
  end
19
25
 
20
- module UltraVerboseWarningFormattter
21
- BASE_URL_FOR_HELP_LINK = 'https://github.com/troessner/reek/wiki/'
22
-
23
- module_function
26
+ #
27
+ # Basic formatter that just shows a simple message for each warning,
28
+ # pre-pended with the result of the passed-in location formatter.
29
+ #
30
+ class SimpleWarningFormatter
31
+ def initialize(location_formatter = BlankLocationFormatter)
32
+ @location_formatter = location_formatter
33
+ end
24
34
 
25
35
  def format(warning)
26
- "#{WarningFormatterWithLineNumbers.format(warning)} " \
27
- "[#{explanatory_link(warning)}]"
36
+ "#{@location_formatter.format(warning)}#{base_format(warning)}"
28
37
  end
29
38
 
30
- def explanatory_link(warning)
31
- "#{BASE_URL_FOR_HELP_LINK}#{class_name_to_param(warning.smell_type)}"
32
- end
39
+ private
33
40
 
34
- def class_name_to_param(name)
35
- name.split(/(?=[A-Z])/).join('-')
41
+ def base_format(warning)
42
+ "#{warning.context} #{warning.message} (#{warning.smell_type})"
36
43
  end
37
44
  end
38
45
 
39
- module SimpleWarningFormatter
40
- def self.format(warning)
41
- "#{warning.context} #{warning.message} (#{warning.smell_type})"
46
+ #
47
+ # Formatter that adds a link to the Wiki to the basic message from
48
+ # SimpleWarningFormatter.
49
+ #
50
+ class WikiLinkWarningFormatter < SimpleWarningFormatter
51
+ BASE_URL_FOR_HELP_LINK = 'https://github.com/troessner/reek/wiki/'
52
+
53
+ def format(warning)
54
+ "#{super} " \
55
+ "[#{explanatory_link(warning)}]"
42
56
  end
43
- end
44
57
 
45
- module WarningFormatterWithLineNumbers
46
- def self.format(warning)
47
- "#{warning.lines.inspect}:#{SimpleWarningFormatter.format(warning)}"
58
+ def explanatory_link(warning)
59
+ "#{BASE_URL_FOR_HELP_LINK}#{class_name_to_param(warning.smell_type)}"
48
60
  end
49
- end
50
61
 
51
- module SingleLineWarningFormatter
52
- def self.format(warning)
53
- "#{warning.source}:#{warning.lines.first}: " \
54
- "#{SimpleWarningFormatter.format(warning)}"
62
+ def class_name_to_param(name)
63
+ name.split(/(?=[A-Z])/).join('-')
55
64
  end
56
65
  end
57
66
  end
@@ -0,0 +1,45 @@
1
+ module Reek
2
+ module Cli
3
+ module Report
4
+ module HeadingFormatter
5
+ #
6
+ # Base class for heading formatters.
7
+ # Is responsible for formatting the heading emitted for each examiner
8
+ #
9
+ class Base
10
+ attr_reader :report_formatter
11
+
12
+ def initialize(report_formatter)
13
+ @report_formatter = report_formatter
14
+ end
15
+
16
+ def header(examiner)
17
+ if show_header?(examiner)
18
+ report_formatter.header examiner
19
+ else
20
+ ''
21
+ end
22
+ end
23
+ end
24
+
25
+ #
26
+ # Lists out each examiner, even if it has no smell
27
+ #
28
+ class Verbose < Base
29
+ def show_header?(_examiner)
30
+ true
31
+ end
32
+ end
33
+
34
+ #
35
+ # Lists only smelly examiners
36
+ #
37
+ class Quiet < Base
38
+ def show_header?(examiner)
39
+ examiner.smelly?
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,23 @@
1
+ module Reek
2
+ module Cli
3
+ module Report
4
+ module BlankLocationFormatter
5
+ def self.format(_warning)
6
+ ''
7
+ end
8
+ end
9
+
10
+ module DefaultLocationFormatter
11
+ def self.format(warning)
12
+ "#{warning.lines.inspect}:"
13
+ end
14
+ end
15
+
16
+ module SingleLineLocationFormatter
17
+ def self.format(warning)
18
+ "#{warning.source}:#{warning.lines.first}: "
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -11,13 +11,9 @@ module Reek
11
11
  NO_WARNINGS_COLOR = :green
12
12
  WARNINGS_COLOR = :red
13
13
 
14
- def initialize(options = {})
15
- @warning_formatter = options.fetch :warning_formatter, SimpleWarningFormatter
16
- @report_formatter = options.fetch :report_formatter, Formatter
14
+ def initialize(_options = {})
17
15
  @examiners = []
18
16
  @total_smell_count = 0
19
- @sort_by_issue_count = options.fetch :sort_by_issue_count, false
20
- @strategy = options.fetch(:strategy, Strategy::Quiet)
21
17
  end
22
18
 
23
19
  def add_examiner(examiner)
@@ -31,7 +27,7 @@ module Reek
31
27
  end
32
28
 
33
29
  def smells
34
- @strategy.new(@report_formatter, @warning_formatter, @examiners).gather_results
30
+ @examiners.map(&:smells).flatten
35
31
  end
36
32
  end
37
33
 
@@ -39,12 +35,26 @@ module Reek
39
35
  # Generates a sorted, text summary of smells in examiners
40
36
  #
41
37
  class TextReport < Base
38
+ def initialize(options = {})
39
+ super options
40
+ @options = options
41
+ @warning_formatter = options.fetch :warning_formatter, SimpleWarningFormatter.new
42
+ @report_formatter = options.fetch :report_formatter, Formatter
43
+ @sort_by_issue_count = options.fetch :sort_by_issue_count, false
44
+ end
45
+
42
46
  def show
43
47
  sort_examiners if smells?
44
48
  display_summary
45
49
  display_total_smell_count
46
50
  end
47
51
 
52
+ def smells
53
+ @examiners.each_with_object([]) do |examiner, result|
54
+ result << summarize_single_examiner(examiner)
55
+ end
56
+ end
57
+
48
58
  private
49
59
 
50
60
  def display_summary
@@ -57,6 +67,16 @@ module Reek
57
67
  print total_smell_count_message
58
68
  end
59
69
 
70
+ def summarize_single_examiner(examiner)
71
+ result = heading_formatter.header(examiner)
72
+ if examiner.smelly?
73
+ formatted_list = @report_formatter.format_list(examiner.smells,
74
+ @warning_formatter)
75
+ result += ":\n#{formatted_list}"
76
+ end
77
+ result
78
+ end
79
+
60
80
  def sort_examiners
61
81
  @examiners.sort_by!(&:smells_count).reverse! if @sort_by_issue_count
62
82
  end
@@ -66,19 +86,19 @@ module Reek
66
86
  s = @total_smell_count == 1 ? '' : 's'
67
87
  Rainbow("#{@total_smell_count} total warning#{s}\n").color(colour)
68
88
  end
89
+
90
+ def heading_formatter
91
+ @heading_formatter ||=
92
+ @options.fetch(:heading_formatter, HeadingFormatter::Quiet).new(@report_formatter)
93
+ end
69
94
  end
70
95
 
71
96
  #
72
97
  # Displays a list of smells in YAML format
73
98
  # YAML with empty array for 0 smells
74
99
  class YamlReport < Base
75
- def initialize(options = {})
76
- @options = options
77
- super options.merge!(strategy: Strategy::Normal)
78
- end
79
-
80
100
  def show
81
- print(smells.to_yaml)
101
+ print smells.map(&:yaml_hash).to_yaml
82
102
  end
83
103
  end
84
104
 
@@ -86,11 +106,6 @@ module Reek
86
106
  # Saves the report as a HTML file
87
107
  #
88
108
  class HtmlReport < Base
89
- def initialize(options = {})
90
- @options = options
91
- super @options.merge!(strategy: Strategy::Normal)
92
- end
93
-
94
109
  require 'erb'
95
110
 
96
111
  def show
@@ -13,9 +13,9 @@ module Reek
13
13
  class << self
14
14
  attr_reader :configuration
15
15
 
16
- def initialize_with(application)
16
+ def initialize_with(options)
17
17
  @has_been_initialized = true
18
- configuration_file_path = ConfigurationFileFinder.find(application)
18
+ configuration_file_path = ConfigurationFileFinder.find(options)
19
19
  return unless configuration_file_path
20
20
  load_from_file configuration_file_path
21
21
  end
@@ -6,26 +6,26 @@ module Reek
6
6
  # ConfigurationFileFinder is responsible for finding reeks configuration.
7
7
  #
8
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
9
+ # 1. Using the cli "-c" switch
10
+ # 2. Having a file ending with .reek either in your current working
11
+ # directory or in a parent directory
12
12
  # 3. Having a file ending with .reek in your HOME directory
13
13
  #
14
- # The order in which ConfigurationFileFinder tries to find such a configuration file is exactly
15
- # like above.
14
+ # The order in which ConfigurationFileFinder tries to find such a
15
+ # configuration file is exactly like above.
16
16
  module ConfigurationFileFinder
17
17
  class << self
18
- def find(application)
19
- configuration_by_cli(application) ||
18
+ def find(options)
19
+ configuration_by_cli(options) ||
20
20
  configuration_in_file_system ||
21
21
  configuration_in_home_directory
22
22
  end
23
23
 
24
24
  private
25
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
26
+ def configuration_by_cli(options)
27
+ return unless options # Return gracefully allowing calls without app context
28
+ config_file_option = options.config_file
29
29
  return unless config_file_option
30
30
  path_name = Pathname.new config_file_option
31
31
  raise ArgumentError, "Config file #{path_name} doesn't exist" unless path_name.exist?
@@ -1,4 +1,5 @@
1
1
  require 'reek/smells'
2
+ require 'reek/smells/smell_detector'
2
3
 
3
4
  module Reek
4
5
  module Core
@@ -9,36 +10,10 @@ module Reek
9
10
  attr_reader :detectors
10
11
 
11
12
  def self.smell_types
12
- # SMELL: Duplication -- these should be loaded by listing the files
13
- [
14
- Smells::Attribute,
15
- Smells::BooleanParameter,
16
- Smells::ClassVariable,
17
- Smells::ControlParameter,
18
- Smells::DataClump,
19
- Smells::DuplicateMethodCall,
20
- Smells::FeatureEnvy,
21
- Smells::IrresponsibleModule,
22
- Smells::LongParameterList,
23
- Smells::LongYieldList,
24
- Smells::ModuleInitialize,
25
- Smells::NestedIterators,
26
- Smells::NilCheck,
27
- Smells::PrimaDonnaMethod,
28
- Smells::RepeatedConditional,
29
- Smells::TooManyInstanceVariables,
30
- Smells::TooManyMethods,
31
- Smells::TooManyStatements,
32
- Smells::UncommunicativeMethodName,
33
- Smells::UncommunicativeModuleName,
34
- Smells::UncommunicativeParameterName,
35
- Smells::UncommunicativeVariableName,
36
- Smells::UnusedParameters,
37
- Smells::UtilityFunction
38
- ]
13
+ Reek::Smells::SmellDetector.descendants
39
14
  end
40
15
 
41
- def initialize(source_description, smell_types = SmellRepository.smell_types)
16
+ def initialize(source_description, smell_types = self.class.smell_types)
42
17
  @typed_detectors = nil
43
18
  @detectors = {}
44
19
  smell_types.each do |klass|
@@ -3,6 +3,7 @@
3
3
  require 'rake'
4
4
  require 'rake/tasklib'
5
5
  require 'pathname'
6
+ require 'English'
6
7
 
7
8
  module Reek
8
9
  #
@@ -32,126 +33,84 @@ module Reek
32
33
  # rake reek REEK_OPTS=-s # sorts the report by smell
33
34
  #
34
35
  class Task < ::Rake::TaskLib
35
- # Name of reek task.
36
- # Defaults to :reek.
37
- attr_accessor :name
36
+ # Name of reek task. Defaults to :reek.
37
+ attr_writer :name
38
38
 
39
- # Array of directories to be added to $LOAD_PATH before running reek.
40
- # Defaults to ['<the absolute path to reek's lib directory>']
41
- attr_accessor :libs
42
-
43
- # Glob pattern to match config files.
39
+ # Path to reeks config file.
44
40
  # Setting the REEK_CFG environment variable overrides this.
45
- # Defaults to 'config/**/*.reek'.
46
- attr_accessor :config_file
47
-
48
- def config_files=(value)
49
- @config_file = value
50
- end
51
-
52
- def config_files
53
- @config_file
54
- end
41
+ attr_writer :config_file
55
42
 
56
43
  # Glob pattern to match source files.
57
44
  # Setting the REEK_SRC environment variable overrides this.
58
45
  # Defaults to 'lib/**/*.rb'.
59
- attr_accessor :source_files
46
+ attr_writer :source_files
60
47
 
61
48
  # String containing commandline options to be passed to Reek.
62
49
  # Setting the REEK_OPTS environment variable overrides this value.
63
50
  # Defaults to ''.
64
- attr_accessor :reek_opts
65
-
66
- # Array of commandline options to pass to ruby. Defaults to [].
67
- attr_accessor :ruby_opts
51
+ attr_writer :reek_opts
68
52
 
69
53
  # Whether or not to fail Rake when an error occurs (typically when smells are found).
70
54
  # Defaults to true.
71
- attr_accessor :fail_on_error
55
+ attr_writer :fail_on_error
72
56
 
73
57
  # Use verbose output. If this is set to true, the task will print
74
58
  # the reek command to stdout. Defaults to false.
75
- attr_accessor :verbose
59
+ attr_writer :verbose
76
60
 
77
- # Defines a new task, using the name +name+.
78
61
  def initialize(name = :reek)
79
- @name = name
80
- @libs = [File.expand_path(File.dirname(__FILE__) + '/../../../lib')]
81
- @config_file = nil
82
- @source_files = nil
83
- @ruby_opts = []
84
- @reek_opts = ''
62
+ @name = name
63
+ @reek_opts = ''
85
64
  @fail_on_error = true
86
- @sort = nil
65
+ @source_files = 'lib/**/*.rb'
66
+ @verbose = false
87
67
 
88
68
  yield self if block_given?
89
- @config_file ||= Pathname.glob('config/**/*.reek').detect(&:file?)
90
- @source_files ||= 'lib/**/*.rb'
91
- define
69
+ define_task
92
70
  end
93
71
 
94
72
  private
95
73
 
96
- def define # :nodoc:
97
- desc 'Check for code smells' unless ::Rake.application.last_comment
98
- task(name) { run_task }
99
- self
74
+ def define_task
75
+ desc 'Check for code smells'
76
+ task(@name) { run_task }
100
77
  end
101
78
 
102
79
  def run_task
103
- return if source_file_list.empty?
104
- cmd = cmd_words.join(' ')
105
- puts cmd if @verbose
106
- raise('Smells found!') if !system(cmd) && fail_on_error
80
+ puts "\n\n!!! Running 'reek' rake command: #{command}\n\n" if @verbose
81
+ system(*command)
82
+ abort("\n\n!!! `reek` has found smells - exiting!") if sys_call_failed? && @fail_on_error
107
83
  end
108
84
 
109
- def self.ruby_exe
110
- File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'])
85
+ def command
86
+ ['reek', *config_file_as_argument, *reek_opts_as_arguments, *source_files].
87
+ compact.
88
+ reject(&:empty?)
111
89
  end
112
90
 
113
- def cmd_words
114
- [Task.ruby_exe] +
115
- ruby_options +
116
- [%(reek)] +
117
- [sort_option] +
118
- config_option +
119
- source_file_list.map { |fn| %("#{fn}") }
91
+ def source_files
92
+ FileList[ENV['REEK_SRC'] || @source_files]
120
93
  end
121
94
 
122
- def config_option
123
- if config_file
124
- ['-c', config_file]
125
- else
126
- []
127
- end
95
+ def reek_opts
96
+ ENV['REEK_OPTS'] || @reek_opts
128
97
  end
129
98
 
130
99
  def config_file
131
100
  ENV['REEK_CFG'] || @config_file
132
101
  end
133
102
 
134
- def ruby_options
135
- if bundler?
136
- %w(-S bundle exec)
137
- else
138
- lib_path = @libs.join(File::PATH_SEPARATOR)
139
- @ruby_opts.clone << "-I\"#{lib_path}\""
140
- end
103
+ def sys_call_failed?
104
+ !$CHILD_STATUS.success?
141
105
  end
142
106
 
143
- def bundler?
144
- File.exist?('./Gemfile')
145
- end
146
-
147
- def sort_option
148
- ENV['REEK_OPTS'] || @reek_opts
107
+ def config_file_as_argument
108
+ return [] unless @config_file
109
+ ['-c', @config_file]
149
110
  end
150
111
 
151
- def source_file_list # :nodoc:
152
- files = ENV['REEK_SRC'] || @source_files
153
- return [] unless files
154
- FileList[files]
112
+ def reek_opts_as_arguments
113
+ reek_opts.split(/\s+/)
155
114
  end
156
115
  end
157
116
  end