reek 5.4.0 → 6.0.1

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +27 -6
  4. data/.rubocop_todo.yml +4 -4
  5. data/.simplecov +1 -0
  6. data/.travis.yml +13 -11
  7. data/CHANGELOG.md +27 -1
  8. data/Dockerfile +2 -1
  9. data/Gemfile +15 -17
  10. data/README.md +15 -11
  11. data/bin/code_climate_reek +12 -2
  12. data/docs/Attribute.md +1 -1
  13. data/docs/Control-Couple.md +1 -1
  14. data/docs/Nil-Check.md +4 -1
  15. data/features/command_line_interface/options.feature +2 -3
  16. data/features/reports/codeclimate.feature +2 -2
  17. data/features/reports/json.feature +3 -3
  18. data/features/reports/reports.feature +4 -4
  19. data/features/reports/yaml.feature +3 -3
  20. data/features/step_definitions/reek_steps.rb +4 -0
  21. data/features/support/env.rb +1 -2
  22. data/lib/reek/ast/sexp_extensions/arguments.rb +11 -0
  23. data/lib/reek/cli/command/todo_list_command.rb +7 -2
  24. data/lib/reek/cli/options.rb +2 -2
  25. data/lib/reek/code_comment.rb +45 -38
  26. data/lib/reek/configuration/configuration_converter.rb +2 -2
  27. data/lib/reek/configuration/directory_directives.rb +7 -1
  28. data/lib/reek/errors/legacy_comment_separator_error.rb +36 -0
  29. data/lib/reek/examiner.rb +3 -3
  30. data/lib/reek/report.rb +5 -7
  31. data/lib/reek/report/code_climate/code_climate_report.rb +2 -1
  32. data/lib/reek/report/simple_warning_formatter.rb +0 -7
  33. data/lib/reek/report/text_report.rb +2 -2
  34. data/lib/reek/smell_detectors/base_detector.rb +2 -10
  35. data/lib/reek/smell_detectors/data_clump.rb +23 -56
  36. data/lib/reek/smell_detectors/nil_check.rb +1 -12
  37. data/lib/reek/smell_detectors/subclassed_from_core_class.rb +3 -7
  38. data/lib/reek/smell_detectors/too_many_constants.rb +1 -1
  39. data/lib/reek/smell_warning.rb +18 -14
  40. data/lib/reek/source/source_code.rb +3 -2
  41. data/lib/reek/spec/smell_matcher.rb +2 -1
  42. data/lib/reek/version.rb +1 -1
  43. data/reek.gemspec +5 -6
  44. data/spec/reek/ast/sexp_extensions_spec.rb +15 -33
  45. data/spec/reek/cli/application_spec.rb +1 -1
  46. data/spec/reek/code_comment_spec.rb +41 -42
  47. data/spec/reek/configuration/directory_directives_spec.rb +6 -0
  48. data/spec/reek/context_builder_spec.rb +110 -113
  49. data/spec/reek/examiner_spec.rb +1 -0
  50. data/spec/reek/report/code_climate/code_climate_configuration_spec.rb +1 -3
  51. data/spec/reek/report/code_climate/code_climate_fingerprint_spec.rb +26 -26
  52. data/spec/reek/report/code_climate/code_climate_formatter_spec.rb +6 -6
  53. data/spec/reek/report/code_climate/code_climate_report_spec.rb +1 -1
  54. data/spec/reek/report/json_report_spec.rb +1 -1
  55. data/spec/reek/report/location_formatter_spec.rb +3 -3
  56. data/spec/reek/report/text_report_spec.rb +1 -7
  57. data/spec/reek/report/yaml_report_spec.rb +1 -1
  58. data/spec/reek/smell_configuration_spec.rb +2 -0
  59. data/spec/reek/smell_detectors/base_detector_spec.rb +3 -16
  60. data/spec/reek/smell_detectors/data_clump_spec.rb +14 -0
  61. data/spec/reek/smell_detectors/missing_safe_method_spec.rb +8 -2
  62. data/spec/reek/smell_detectors/nil_check_spec.rb +3 -3
  63. data/spec/reek/smell_warning_spec.rb +17 -28
  64. data/spec/reek/source/source_code_spec.rb +13 -0
  65. data/spec/reek/spec/should_reek_of_spec.rb +0 -1
  66. data/spec/reek/spec/should_reek_only_of_spec.rb +6 -13
  67. data/spec/reek/spec/smell_matcher_spec.rb +1 -2
  68. data/spec/spec_helper.rb +20 -6
  69. metadata +11 -26
  70. data/spec/factories/factories.rb +0 -48
@@ -24,7 +24,7 @@ Feature: Report smells using simple JSON layout
24
24
  "context": "Smelly#x",
25
25
  "lines": [ 4 ],
26
26
  "message": "has the name 'x'",
27
- "documentation_link": "https://github.com/troessner/reek/blob/v5.4.0/docs/Uncommunicative-Method-Name.md",
27
+ "documentation_link": "https://github.com/troessner/reek/blob/v6.0.1/docs/Uncommunicative-Method-Name.md",
28
28
  "name": "x"
29
29
  },
30
30
  {
@@ -33,7 +33,7 @@ Feature: Report smells using simple JSON layout
33
33
  "context": "Smelly#x",
34
34
  "lines": [ 5 ],
35
35
  "message": "has the variable name 'y'",
36
- "documentation_link": "https://github.com/troessner/reek/blob/v5.4.0/docs/Uncommunicative-Variable-Name.md",
36
+ "documentation_link": "https://github.com/troessner/reek/blob/v6.0.1/docs/Uncommunicative-Variable-Name.md",
37
37
  "name": "y"
38
38
  }
39
39
  ]
@@ -53,7 +53,7 @@ Feature: Report smells using simple JSON layout
53
53
  1
54
54
  ],
55
55
  "message": "has no descriptive comment",
56
- "documentation_link": "https://github.com/troessner/reek/blob/v5.4.0/docs/Irresponsible-Module.md"
56
+ "documentation_link": "https://github.com/troessner/reek/blob/v6.0.1/docs/Irresponsible-Module.md"
57
57
  }
58
58
  ]
59
59
  """
@@ -182,8 +182,8 @@ Feature: Correctly formatted reports
182
182
  And it reports:
183
183
  """
184
184
  smelly.rb -- 2 warnings:
185
- [4]:UncommunicativeMethodName: Smelly#x has the name 'x' [https://github.com/troessner/reek/blob/v5.4.0/docs/Uncommunicative-Method-Name.md]
186
- [5]:UncommunicativeVariableName: Smelly#x has the variable name 'y' [https://github.com/troessner/reek/blob/v5.4.0/docs/Uncommunicative-Variable-Name.md]
185
+ [4]:UncommunicativeMethodName: Smelly#x has the name 'x' [https://github.com/troessner/reek/blob/v6.0.1/docs/Uncommunicative-Method-Name.md]
186
+ [5]:UncommunicativeVariableName: Smelly#x has the variable name 'y' [https://github.com/troessner/reek/blob/v6.0.1/docs/Uncommunicative-Variable-Name.md]
187
187
  """
188
188
 
189
189
  Examples:
@@ -209,8 +209,8 @@ Feature: Correctly formatted reports
209
209
  And it reports:
210
210
  """
211
211
  smelly.rb -- 2 warnings:
212
- UncommunicativeMethodName: Smelly#x has the name 'x' [https://github.com/troessner/reek/blob/v5.4.0/docs/Uncommunicative-Method-Name.md]
213
- UncommunicativeVariableName: Smelly#x has the variable name 'y' [https://github.com/troessner/reek/blob/v5.4.0/docs/Uncommunicative-Variable-Name.md]
212
+ UncommunicativeMethodName: Smelly#x has the name 'x' [https://github.com/troessner/reek/blob/v6.0.1/docs/Uncommunicative-Method-Name.md]
213
+ UncommunicativeVariableName: Smelly#x has the variable name 'y' [https://github.com/troessner/reek/blob/v6.0.1/docs/Uncommunicative-Variable-Name.md]
214
214
  """
215
215
 
216
216
  Examples:
@@ -25,7 +25,7 @@ Feature: Report smells using simple YAML layout
25
25
  smell_type: UncommunicativeMethodName
26
26
  source: smelly.rb
27
27
  name: x
28
- documentation_link: https://github.com/troessner/reek/blob/v5.4.0/docs/Uncommunicative-Method-Name.md
28
+ documentation_link: https://github.com/troessner/reek/blob/v6.0.1/docs/Uncommunicative-Method-Name.md
29
29
  - context: Smelly#x
30
30
  lines:
31
31
  - 5
@@ -33,7 +33,7 @@ Feature: Report smells using simple YAML layout
33
33
  smell_type: UncommunicativeVariableName
34
34
  source: smelly.rb
35
35
  name: y
36
- documentation_link: https://github.com/troessner/reek/blob/v5.4.0/docs/Uncommunicative-Variable-Name.md
36
+ documentation_link: https://github.com/troessner/reek/blob/v6.0.1/docs/Uncommunicative-Variable-Name.md
37
37
  """
38
38
 
39
39
  Scenario: Indicate smells and print them as yaml when using STDIN
@@ -48,5 +48,5 @@ Feature: Report smells using simple YAML layout
48
48
  lines:
49
49
  - 1
50
50
  message: has no descriptive comment
51
- documentation_link: https://github.com/troessner/reek/blob/v5.4.0/docs/Irresponsible-Module.md
51
+ documentation_link: https://github.com/troessner/reek/blob/v6.0.1/docs/Irresponsible-Module.md
52
52
  """
@@ -2,6 +2,10 @@ When /^I run reek (.*)$/ do |args|
2
2
  reek(args)
3
3
  end
4
4
 
5
+ When 'I run the code climate reek runner' do
6
+ run_command_and_stop 'code_climate_reek'
7
+ end
8
+
5
9
  When /^I pass "([^\"]*)" to reek *(.*)$/ do |stdin, args|
6
10
  reek_with_pipe(stdin, args)
7
11
  end
@@ -1,11 +1,10 @@
1
1
  require_relative '../../lib/reek'
2
2
  require_relative '../../lib/reek/cli/application'
3
3
  require 'aruba/cucumber'
4
- require 'active_support/core_ext/string/strip'
5
4
 
6
5
  begin
7
6
  require 'pry-byebug'
8
- rescue LoadError # rubocop:disable Lint/HandleExceptions
7
+ rescue LoadError # rubocop:disable Lint/SuppressedException
9
8
  end
10
9
 
11
10
  #
@@ -93,6 +93,17 @@ module Reek
93
93
  module ShadowargNode
94
94
  include ArgNodeBase
95
95
  end
96
+
97
+ # Utility methods for :forward_args nodes.
98
+ # rubocop:disable Naming/ClassAndModuleCamelCase
99
+ module Forward_ArgsNode
100
+ include ArgNodeBase
101
+
102
+ def anonymous_splat?
103
+ true
104
+ end
105
+ end
106
+ # rubocop:enable Naming/ClassAndModuleCamelCase
96
107
  end
97
108
  end
98
109
  end
@@ -40,13 +40,18 @@ module Reek
40
40
  def groups
41
41
  @groups ||=
42
42
  begin
43
- todos = smells.group_by(&:smell_class).map do |smell_class, smells_for_class|
43
+ todos = DetectorRepository.smell_types.map do |smell_class|
44
+ smells_for_class = grouped_smells[smell_class.smell_type] or next
44
45
  smell_class.todo_configuration_for(smells_for_class)
45
46
  end
46
- todos.inject(&:merge)
47
+ todos.compact.inject(&:merge)
47
48
  end
48
49
  end
49
50
 
51
+ def grouped_smells
52
+ @grouped_smells ||= smells.group_by(&:smell_type)
53
+ end
54
+
50
55
  # :reek:FeatureEnvy
51
56
  def write_to_file
52
57
  File.open(DEFAULT_CONFIGURATION_FILE_NAME, 'w') do |configuration_file|
@@ -131,9 +131,9 @@ module Reek
131
131
  def set_alternative_formatter_options
132
132
  parser.separator "\nReport format:"
133
133
  parser.on(
134
- '-f', '--format FORMAT', [:html, :text, :yaml, :json, :xml, :code_climate],
134
+ '-f', '--format FORMAT', [:html, :text, :yaml, :json, :xml],
135
135
  'Report smells in the given format:',
136
- ' html', ' text (default)', ' yaml', ' json', ' xml', ' code_climate') do |opt|
136
+ ' html', ' text (default)', ' yaml', ' json', ' xml') do |opt|
137
137
  self.report_format = opt
138
138
  end
139
139
  end
@@ -6,6 +6,7 @@ require_relative 'smell_detectors/base_detector'
6
6
  require_relative 'errors/bad_detector_in_comment_error'
7
7
  require_relative 'errors/bad_detector_configuration_key_in_comment_error'
8
8
  require_relative 'errors/garbage_detector_configuration_in_comment_error'
9
+ require_relative 'errors/legacy_comment_separator_error'
9
10
 
10
11
  module Reek
11
12
  #
@@ -14,12 +15,10 @@ module Reek
14
15
  #
15
16
  class CodeComment
16
17
  CONFIGURATION_REGEX = /
17
- :reek: # prefix
18
- (\w+) # smell detector e.g.: UncommunicativeVariableName
19
- (
20
- \s*
21
- (\{.*?\}) # optional details in hash style e.g.: { max_methods: 30 }
22
- )?
18
+ :reek: # prefix
19
+ (\w+) # smell detector e.g.: UncommunicativeVariableName
20
+ (:?\s*) # separator
21
+ (\{.*?\})? # details in hash style e.g.: { max_methods: 30 }
23
22
  /x.freeze
24
23
  SANITIZE_REGEX = /(#|\n|\s)+/.freeze # Matches '#', newlines and > 1 whitespaces.
25
24
  DISABLE_DETECTOR_CONFIGURATION = '{ enabled: false }'
@@ -38,7 +37,8 @@ module Reek
38
37
  @source = source
39
38
  @config = Hash.new { |hash, key| hash[key] = {} }
40
39
 
41
- @original_comment.scan(CONFIGURATION_REGEX) do |detector_name, _option_string, options|
40
+ @original_comment.scan(CONFIGURATION_REGEX) do |detector_name, separator, options|
41
+ escalate_legacy_separator separator
42
42
  CodeCommentValidator.new(detector_name: detector_name,
43
43
  original_comment: original_comment,
44
44
  line: line,
@@ -64,6 +64,14 @@ module Reek
64
64
  strip
65
65
  end
66
66
 
67
+ def escalate_legacy_separator(separator)
68
+ return unless separator.start_with? ':'
69
+
70
+ raise Errors::LegacyCommentSeparatorError.new(original_comment: original_comment,
71
+ source: source,
72
+ line: line)
73
+ end
74
+
67
75
  #
68
76
  # A typical configuration via code comment looks like this:
69
77
  #
@@ -88,25 +96,22 @@ module Reek
88
96
  # @param source [String] path to source file or "string"
89
97
  # @param options [String] the configuration options as String for the detector that were
90
98
  # extracted from the original comment
91
- def initialize(detector_name:, original_comment:, line:, source:, options: {})
99
+ def initialize(detector_name:, original_comment:, line:, source:, options:)
92
100
  @detector_name = detector_name
93
101
  @original_comment = original_comment
94
102
  @line = line
95
103
  @source = source
96
104
  @options = options
97
- @detector_class = nil # We only know this one after our first initial checks
98
- @parsed_options = nil # We only know this one after our first initial checks
99
105
  end
100
106
 
101
107
  #
102
108
  # Method can raise the following errors:
109
+ # * Errors::LegacyCommentSeparatorError
103
110
  # * Errors::BadDetectorInCommentError
104
111
  # * Errors::GarbageDetectorConfigurationInCommentError
105
112
  # * Errors::BadDetectorConfigurationKeyInCommentError
106
113
  # @return [undefined]
107
114
  def validate
108
- escalate_bad_detector
109
- escalate_bad_detector_configuration
110
115
  escalate_unknown_configuration_key
111
116
  end
112
117
 
@@ -116,39 +121,41 @@ module Reek
116
121
  :original_comment,
117
122
  :line,
118
123
  :source,
119
- :options,
120
- :detector_class,
121
- :parsed_options
122
-
123
- def escalate_bad_detector
124
- return if SmellDetectors::BaseDetector.valid_detector?(detector_name)
125
-
126
- raise Errors::BadDetectorInCommentError, detector_name: detector_name,
127
- original_comment: original_comment,
128
- source: source,
129
- line: line
130
- end
124
+ :separator,
125
+ :options
131
126
 
132
- def escalate_bad_detector_configuration
133
- @parsed_options = YAML.safe_load(options || CodeComment::DISABLE_DETECTOR_CONFIGURATION,
134
- permitted_classes: [Regexp])
127
+ def parsed_options
128
+ @parsed_options ||= YAML.safe_load(options || CodeComment::DISABLE_DETECTOR_CONFIGURATION,
129
+ permitted_classes: [Regexp])
135
130
  rescue Psych::SyntaxError
136
- raise Errors::GarbageDetectorConfigurationInCommentError, detector_name: detector_name,
137
- original_comment: original_comment,
138
- source: source,
139
- line: line
131
+ raise Errors::GarbageDetectorConfigurationInCommentError.new(detector_name: detector_name,
132
+ original_comment: original_comment,
133
+ source: source,
134
+ line: line)
140
135
  end
141
136
 
142
137
  def escalate_unknown_configuration_key
143
- @detector_class = SmellDetectors::BaseDetector.to_detector(detector_name)
144
-
145
138
  return if given_keys_legit?
146
139
 
147
- raise Errors::BadDetectorConfigurationKeyInCommentError, detector_name: detector_name,
148
- offensive_keys: configuration_keys_difference,
149
- original_comment: original_comment,
150
- source: source,
151
- line: line
140
+ raise Errors::BadDetectorConfigurationKeyInCommentError.new(detector_name: detector_name,
141
+ offensive_keys: configuration_keys_difference,
142
+ original_comment: original_comment,
143
+ source: source,
144
+ line: line)
145
+ end
146
+
147
+ def detector_class
148
+ @detector_class ||= SmellDetectors::BaseDetector.to_detector(detector_name)
149
+ rescue NameError
150
+ raise Errors::BadDetectorInCommentError.new(detector_name: detector_name,
151
+ original_comment: original_comment,
152
+ source: source,
153
+ line: line)
154
+ end
155
+
156
+ # @return [Boolean] comment uses legacy three-colon format
157
+ def legacy_format?
158
+ separator.start_with? ':'
152
159
  end
153
160
 
154
161
  # @return [Boolean] all keys in code comment are applicable to the detector in question
@@ -74,7 +74,7 @@ module Reek
74
74
  return unless configuration[DETECTORS_KEY]
75
75
 
76
76
  configuration[DETECTORS_KEY].tap do |detectors|
77
- detectors.keys.each do |detector|
77
+ detectors.each_key do |detector|
78
78
  convertible_attributes(detectors[detector]).each do |attribute|
79
79
  detectors[detector][attribute] = detectors[detector][attribute].map do |item|
80
80
  to_regex item
@@ -94,7 +94,7 @@ module Reek
94
94
  return unless configuration[DIRECTORIES_KEY]
95
95
 
96
96
  configuration[DIRECTORIES_KEY].tap do |directories|
97
- directories.keys.each do |directory|
97
+ directories.each_key do |directory|
98
98
  directories[directory].each do |detector, configuration|
99
99
  convertible_attributes(configuration).each do |attribute|
100
100
  directories[directory][detector][attribute] = directories[directory][detector][attribute].map do |item|
@@ -53,10 +53,16 @@ module Reek
53
53
  # @quality :reek:FeatureEnvy
54
54
  def best_match_for(source_base_dir)
55
55
  keys.
56
- select { |pathname| source_base_dir.to_s.match(glob_to_regexp(pathname.to_s)) }.
56
+ select do |pathname|
57
+ match?(source_base_dir, pathname) || match?(source_base_dir, pathname.expand_path)
58
+ end.
57
59
  max_by { |pathname| pathname.to_s.length }
58
60
  end
59
61
 
62
+ def match?(source_base_dir, pathname)
63
+ source_base_dir.to_s.match(glob_to_regexp(pathname.to_s))
64
+ end
65
+
60
66
  # Transform a glob pattern to a regexp.
61
67
  #
62
68
  # It changes:
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_error'
4
+
5
+ module Reek
6
+ module Errors
7
+ # Gets raised for old-style comment configuration format.
8
+ class LegacyCommentSeparatorError < BaseError
9
+ MESSAGE = <<-MESSAGE
10
+ Error: You are using the legacy configuration format (including three
11
+ colons) to configure Reek in one your source code comments.
12
+
13
+ The source is '%<source>s' and the comment belongs to the expression
14
+ starting in line %<line>d.
15
+
16
+ Here's the original comment:
17
+
18
+ %<comment>s
19
+
20
+ Please see the Reek docs for information on how to configure Reek via
21
+ source code comments: #{DocumentationLink.build('Smell Suppression')}
22
+
23
+ Update the offensive comment and re-run Reek.
24
+
25
+ MESSAGE
26
+
27
+ def initialize(source:, line:, original_comment:)
28
+ message = format(MESSAGE,
29
+ source: source,
30
+ line: line,
31
+ comment: original_comment)
32
+ super message
33
+ end
34
+ end
35
+ end
36
+ end
@@ -105,11 +105,11 @@ module Reek
105
105
  rescue Errors::BaseError
106
106
  raise
107
107
  rescue EncodingError
108
- raise Errors::EncodingError, origin: origin
108
+ raise Errors::EncodingError.new(origin: origin)
109
109
  rescue Parser::SyntaxError
110
- raise Errors::SyntaxError, origin: origin
110
+ raise Errors::SyntaxError.new(origin: origin)
111
111
  rescue StandardError
112
- raise Errors::IncomprehensibleSourceError, origin: origin
112
+ raise Errors::IncomprehensibleSourceError.new(origin: origin)
113
113
  end
114
114
 
115
115
  def syntax_tree
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'report/code_climate'
4
3
  require_relative 'report/html_report'
5
4
  require_relative 'report/json_report'
6
5
  require_relative 'report/text_report'
@@ -17,12 +16,11 @@ module Reek
17
16
  # Reek reporting functionality.
18
17
  module Report
19
18
  REPORT_CLASSES = {
20
- yaml: YAMLReport,
21
- json: JSONReport,
22
- html: HTMLReport,
23
- xml: XMLReport,
24
- text: TextReport,
25
- code_climate: CodeClimateReport
19
+ yaml: YAMLReport,
20
+ json: JSONReport,
21
+ html: HTMLReport,
22
+ xml: XMLReport,
23
+ text: TextReport
26
24
  }.freeze
27
25
 
28
26
  LOCATION_FORMATTERS = {
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../base_report'
4
+ require_relative 'code_climate_formatter'
4
5
 
5
6
  module Reek
6
7
  module Report
@@ -12,7 +13,7 @@ module Reek
12
13
  class CodeClimateReport < BaseReport
13
14
  def show(out = $stdout)
14
15
  smells.map do |smell|
15
- out.print warning_formatter.format_code_climate_hash(smell)
16
+ out.print CodeClimateFormatter.new(smell).render
16
17
  end
17
18
  end
18
19
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'code_climate/code_climate_formatter'
4
-
5
3
  module Reek
6
4
  module Report
7
5
  #
@@ -17,11 +15,6 @@ module Reek
17
15
  "#{location_formatter.format(warning)}#{warning.base_message}"
18
16
  end
19
17
 
20
- # @quality :reek:UtilityFunction
21
- def format_code_climate_hash(warning)
22
- CodeClimateFormatter.new(warning).render
23
- end
24
-
25
18
  def format_list(warnings)
26
19
  warnings.map { |warning| " #{format(warning)}" }.join("\n")
27
20
  end