reek 4.7.0 → 4.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1ec8e3f40eb231654c58d4025080c703268e7322
4
- data.tar.gz: a018b00cdba03a5da356e1c05e32bd0bc04ba569
3
+ metadata.gz: baf611107a9ba063fb9882a312ae27c556f4a4f6
4
+ data.tar.gz: f56386a2775e1a178a1df9af568055c5ac63c80f
5
5
  SHA512:
6
- metadata.gz: 78c20b5e99a3c4f804c530da98b020d634b289ea575f15339e3978088f207112a022345695b7ba45fcaa5edfbac74bcba38409e99ffc6e42d0e46f2add060fdf
7
- data.tar.gz: 1bd64f7d805994fb356887cb638e900669c2308d2bc86fed37ad36709df4314dbe4dcfc616240c8790bf1d61ce5e3c1b58fd7b07e5bed385d0ced79cfad19cdf
6
+ metadata.gz: d108db6dd4064eced4dfe277238771246120f73874f89ae3800e7bbef91b101d946861cb1793b7a138ce761bf4b26e854ee05b13ca5d39287c46b7c64027436a
7
+ data.tar.gz: 2005c56063bdc5a62f3ec3fe53b73d99158b8b124bdbbb45a3d8ff5ab64ab35e2863afdace05d05b5538855fdc80ed4ca138e073b415ac38f7d9dab95ccbc382
@@ -113,6 +113,10 @@ Style/AccessorMethodName:
113
113
  Exclude:
114
114
  - 'lib/reek/context/visibility_tracker.rb'
115
115
 
116
+ # Allow and/or for control flow only
117
+ Style/AndOr:
118
+ EnforcedStyle: conditionals
119
+
116
120
  Style/Documentation:
117
121
  Exclude:
118
122
  - 'lib/reek/ast/sexp_extensions/send.rb'
@@ -1,5 +1,9 @@
1
1
  # Change log
2
2
 
3
+ ## 4.7.1 (2017-06-12)
4
+
5
+ * (mvz) Improve IrresponsibleModule and fix some bugs along
6
+
3
7
  ## 4.7.0 (2017-05-31)
4
8
 
5
9
  * (pocke) Introduce Syntax smell detector
data/README.md CHANGED
@@ -53,7 +53,7 @@ Reek is a tool that examines Ruby classes, modules and methods and reports any
53
53
 
54
54
  For an excellent introduction to
55
55
  [Code Smells](docs/Code-Smells.md) and Reek check out [this blog post](https://blog.codeship.com/how-to-find-ruby-code-smells-with-reek/)
56
- or [that one](https://troessner.svbtle.com/the-latest-and-greatest-additions-to-reek). There is also [this talk](https://www.youtube.com/watch?v=ZzqOuHI5MkA) from the [RubyConf Portugal](http://rubyconf.pt/).
56
+ or [that one](https://troessner.svbtle.com/the-latest-and-greatest-additions-to-reek). There is also [this talk](https://www.youtube.com/watch?v=pazYe7WRWRU) from [RubyConfBY](http://rubyconference.by/) (there is also a [slide deck](http://talks.chastell.net/rubyconf-by-lt-2016/) if you prefer that).
57
57
 
58
58
  Install it via rubygems:
59
59
 
@@ -0,0 +1,59 @@
1
+ Feature: Report smells using Code Climate format
2
+ In order to run as an Engine on Code Climate, output format following their
3
+ spec.
4
+
5
+ Scenario: output is empty when there are no smells
6
+ Given a directory called 'clean' containing two clean files
7
+ When I run reek --format code_climate clean
8
+ Then it succeeds
9
+ And it reports this Code Climate output:
10
+ """
11
+ """
12
+
13
+ Scenario: Indicate smells and print them as JSON when using files
14
+ Given the smelly file 'smelly.rb'
15
+ When I run reek --format code_climate smelly.rb
16
+ Then it reports this Code Climate output:
17
+ """
18
+ {
19
+ "type": "issue",
20
+ "check_name": "UncommunicativeMethodName",
21
+ "description": "Smelly#x has the name 'x'",
22
+ "categories": [
23
+ "Complexity"
24
+ ],
25
+ "location": {
26
+ "path": "smelly.rb",
27
+ "lines": {
28
+ "begin": 4,
29
+ "end": 4
30
+ }
31
+ },
32
+ "remediation_points": 150000,
33
+ "content": {
34
+ "body": "An `Uncommunicative Method Name` is a method name that doesn't communicate its intent well enough.\n\nPoor names make it hard for the reader to build a mental picture of what's going on in the code. They can also be mis-interpreted; and they hurt the flow of reading, because the reader must slow down to interpret the names.\n"
35
+ },
36
+ "fingerprint": "2b41a3a4bb7de31ac4f5944bf68b7f5f"
37
+ }
38
+ NULL_BYTE_CHARACTER
39
+ {
40
+ "type": "issue",
41
+ "check_name": "UncommunicativeVariableName",
42
+ "description": "Smelly#x has the variable name 'y'",
43
+ "categories": [
44
+ "Complexity"
45
+ ],
46
+ "location": {
47
+ "path": "smelly.rb",
48
+ "lines": {
49
+ "begin": 5,
50
+ "end": 5
51
+ }
52
+ },
53
+ "remediation_points": 150000,
54
+ "content": {
55
+ "body": "An `Uncommunicative Variable Name` is a variable name that doesn't communicate its intent well enough.\n\nPoor names make it hard for the reader to build a mental picture of what's going on in the code. They can also be mis-interpreted; and they hurt the flow of reading, because the reader must slow down to interpret the names.\n"
56
+ },
57
+ "fingerprint": "72f0dc8f8da5f9d7b8b29318636e5609"
58
+ }
59
+ """
@@ -84,3 +84,15 @@ end
84
84
  Then /^it reports the current version$/ do
85
85
  expect(last_command_started).to have_output("reek #{Reek::Version::STRING}")
86
86
  end
87
+
88
+ Then /^it reports this Code Climate output:$/ do |expected_output|
89
+ expected_issues = expected_output.split('NULL_BYTE_CHARACTER').map do |issue|
90
+ JSON.parse(issue)
91
+ end
92
+
93
+ actual_issues = last_command_started.stdout.split("\0").map do |issue|
94
+ JSON.parse(issue)
95
+ end
96
+
97
+ expect(actual_issues).to eq(expected_issues)
98
+ end
@@ -107,6 +107,15 @@ module Reek
107
107
  1
108
108
  end
109
109
 
110
+ # Most nodes represent only one statement (although they can have nested
111
+ # statements). The special type :begin exists primarily to contain more
112
+ # statements.
113
+ #
114
+ # @return Array of unique outer-level statements contained in this node
115
+ def statements
116
+ [self]
117
+ end
118
+
110
119
  def source
111
120
  loc.expression.source_buffer.name
112
121
  end
@@ -4,6 +4,7 @@ require_relative 'reference_collector'
4
4
 
5
5
  require_relative 'sexp_extensions/arguments'
6
6
  require_relative 'sexp_extensions/attribute_assignments'
7
+ require_relative 'sexp_extensions/begin'
7
8
  require_relative 'sexp_extensions/block'
8
9
  require_relative 'sexp_extensions/case'
9
10
  require_relative 'sexp_extensions/constant'
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reek
4
+ module AST
5
+ module SexpExtensions
6
+ # Utility methods for :begin nodes.
7
+ module BeginNode
8
+ # The special type :begin exists primarily to contain more statements.
9
+ # Therefore, this method overrides the default implementation to return
10
+ # this node's children.
11
+ def statements
12
+ children
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -3,9 +3,10 @@
3
3
  module Reek
4
4
  module AST
5
5
  module SexpExtensions
6
- # Base module for utility methods for module nodes.
7
- module ModuleNodeBase
8
- # The full name of the module or class, including the name of any
6
+ # Base module for utility methods for nodes that define constants: module
7
+ # definition, class definition and constant assignment.
8
+ module ConstantDefiningNodeBase
9
+ # The full name of the constant, including the name of any
9
10
  # module or class it is nested inside of.
10
11
  #
11
12
  # For example, given code like this:
@@ -16,7 +17,8 @@ module Reek
16
17
  # end
17
18
  #
18
19
  # The full name for the inner class will be 'Foo::Bar::Baz'. To return
19
- # the correct name, the name of the outer context has to be passed into this method.
20
+ # the correct name, the name of the outer context has to be passed into
21
+ # this method.
20
22
  #
21
23
  # @param outer [String] full name of the wrapping module or class
22
24
  # @return the module's full name
@@ -32,27 +34,71 @@ module Reek
32
34
  def simple_name
33
35
  name.split('::').last
34
36
  end
37
+ end
38
+
39
+ # Base module for utility methods for module nodes.
40
+ module ModuleNodeBase
41
+ include ConstantDefiningNodeBase
35
42
 
36
43
  def name
37
44
  children.first.format_to_ruby
38
45
  end
46
+
47
+ # In the AST, the set of children of a module that a human might identify
48
+ # is coded in three different ways.
49
+ #
50
+ # If there are no children, the last element of the module node is nil,
51
+ # like so:
52
+ #
53
+ # s(:class,
54
+ # s(:const, nil, :C),
55
+ # nil,
56
+ # nil)
57
+ #
58
+ # If there is one child, the last element of the module node is that
59
+ # child, like so:
60
+ #
61
+ # s(:class,
62
+ # s(:const, nil, :C),
63
+ # nil,
64
+ # s(:def, :f, s(:args), nil))
65
+ #
66
+ # If there is more than one child, those are wrapped as children in a
67
+ # node of type :begin, like so:
68
+ #
69
+ # s(:class,
70
+ # s(:const, nil, :Alfa),
71
+ # nil,
72
+ # s(:begin,
73
+ # s(:def, :bravo, s(:args), nil),
74
+ # s(:class, s(:const, nil, :Charlie), nil, nil)))
75
+ #
76
+ # This method unifies those three ways to avoid having to handle them
77
+ # differently.
78
+ #
79
+ # @return an array of directly visible children of the module
80
+ #
81
+ def direct_children
82
+ contents = children.last or return []
83
+ contents.statements
84
+ end
39
85
  end
40
86
 
41
- # Utility methods for :module nodes.
87
+ # Utility methods for module definition (:module) nodes.
42
88
  module ModuleNode
43
89
  include ModuleNodeBase
44
90
  end
45
91
 
46
- # Utility methods for :class nodes.
92
+ # Utility methods for class definition (:class) nodes.
47
93
  module ClassNode
48
94
  include ModuleNodeBase
49
95
 
50
96
  def superclass() children[1] end
51
97
  end
52
98
 
53
- # Utility methods for :casgn nodes.
99
+ # Utility methods for constant assignment (:casgn) nodes.
54
100
  module CasgnNode
55
- include ModuleNodeBase
101
+ include ConstantDefiningNodeBase
56
102
 
57
103
  def defines_module?
58
104
  call = constant_definition
@@ -10,7 +10,6 @@ module Reek
10
10
  #
11
11
  # A context wrapper for any module found in a syntax tree.
12
12
  #
13
- # :reek:FeatureEnvy
14
13
  class ModuleContext < CodeContext
15
14
  attr_reader :visibility_tracker
16
15
 
@@ -74,10 +73,12 @@ module Reek
74
73
  # However, if the module is empty, it is not considered a namespace module.
75
74
  #
76
75
  # @return true if the module is a namespace module
76
+ #
77
+ # :reek:FeatureEnvy
77
78
  def namespace_module?
78
79
  return false if exp.type == :casgn
79
- contents = exp.children.last
80
- contents && contents.find_nodes([:def, :defs], [:casgn, :class, :module]).empty?
80
+ children = exp.direct_children
81
+ children.any? && children.all? { |child| [:casgn, :class, :module].include? child.type }
81
82
  end
82
83
 
83
84
  def track_visibility(visibility, names)
@@ -92,6 +93,8 @@ module Reek
92
93
  names: names
93
94
  end
94
95
 
96
+ private
97
+
95
98
  def instance_method_children
96
99
  children.select(&:instance_method?)
97
100
  end
@@ -7,9 +7,10 @@ module Reek
7
7
  # Check syntax errors.
8
8
  # Note: this detector is called by examiner directly unlike other detectors.
9
9
  class Syntax < BaseDetector
10
- DummyContext = Struct.new(:exp, :full_name).new(
11
- Struct.new('Exp', :source).new(nil),
12
- 'This file')
10
+ # Context duck type for this atypical smell detector
11
+ DummyContext = Struct.new(:exp, :full_name)
12
+ # Exp duck type for this atypical smell detector
13
+ DummyExp = Struct.new(:source)
13
14
 
14
15
  def self.contexts
15
16
  []
@@ -21,9 +22,12 @@ module Reek
21
22
 
22
23
  # :reek:FeatureEnvy
23
24
  def smells_from_source(source)
25
+ context = DummyContext.new(
26
+ DummyExp.new(source.origin),
27
+ 'This file')
24
28
  source.diagnostics.map do |diagnostic|
25
29
  smell_warning(
26
- context: DummyContext,
30
+ context: context,
27
31
  lines: [diagnostic.location.line],
28
32
  message: "has #{diagnostic.message}")
29
33
  end
@@ -12,13 +12,6 @@ module Reek
12
12
  class ShouldReekOf
13
13
  include RSpec::Matchers::Composable
14
14
 
15
- # Variant of Examiner that doesn't swallow exceptions
16
- class UnsafeExaminer < Examiner
17
- def run
18
- examine_tree
19
- end
20
- end
21
-
22
15
  attr_reader :failure_message, :failure_message_when_negated
23
16
 
24
17
  def initialize(smell_type_or_class,
@@ -32,9 +25,9 @@ module Reek
32
25
 
33
26
  def matches?(source)
34
27
  @matching_smell_types = nil
35
- self.examiner = UnsafeExaminer.new(source,
36
- filter_by_smells: [smell_type],
37
- configuration: configuration)
28
+ self.examiner = Examiner.new(source,
29
+ filter_by_smells: [smell_type],
30
+ configuration: configuration)
38
31
  set_failure_messages
39
32
  matching_smell_details?
40
33
  end
@@ -8,6 +8,6 @@ module Reek
8
8
  # @public
9
9
  module Version
10
10
  # @public
11
- STRING = '4.7.0'.freeze
11
+ STRING = '4.7.1'.freeze
12
12
  end
13
13
  end
@@ -18,7 +18,7 @@ RSpec.describe Reek::SmellDetectors::IrresponsibleModule do
18
18
  it 'does count all occurences' do
19
19
  src = <<-EOS
20
20
  class Alfa
21
- # Method is necessary because we don't count empty classes.
21
+ # Method is necessary because we don't count namespace classes.
22
22
  def bravo; end
23
23
  class Charlie
24
24
  end
@@ -40,21 +40,21 @@ RSpec.describe Reek::SmellDetectors::IrresponsibleModule do
40
40
  expect(src).to reek_of(:IrresponsibleModule)
41
41
  end
42
42
 
43
- it "does not report re-opened #{scope}" do
43
+ it "does not report a #{scope} having a comment" do
44
44
  src = <<-EOS
45
- # Abstract base
45
+ # Do not report me, I'm responsible!
46
46
  #{scope} Alfa; end
47
-
48
- #{scope} Alfa; def bravo; end; end
49
47
  EOS
50
48
 
51
49
  expect(src).not_to reek_of(:IrresponsibleModule)
52
50
  end
53
51
 
54
- it "does not report a #{scope} having a comment" do
52
+ it "does not report re-opened #{scope} in the same file" do
55
53
  src = <<-EOS
56
- # Do not report me
54
+ # This comment describes Alfa
57
55
  #{scope} Alfa; end
56
+
57
+ #{scope} Alfa; def bravo; end; end
58
58
  EOS
59
59
 
60
60
  expect(src).not_to reek_of(:IrresponsibleModule)
@@ -73,7 +73,7 @@ RSpec.describe Reek::SmellDetectors::IrresponsibleModule do
73
73
 
74
74
  it "reports a #{scope} with a preceding comment with intermittent material" do
75
75
  src = <<-EOS
76
- # This is a valid comment
76
+ # This is a comment that should not be related to Bravo
77
77
 
78
78
  require 'alfa'
79
79
 
@@ -87,13 +87,13 @@ RSpec.describe Reek::SmellDetectors::IrresponsibleModule do
87
87
  it "reports a #{scope} with only a trailing comment" do
88
88
  src = <<-EOS
89
89
  #{scope} Alfa
90
- end # end scope
90
+ end # This belongs to Alfa but doesn't count
91
91
  EOS
92
92
 
93
93
  expect(src).to reek_of(:IrresponsibleModule)
94
94
  end
95
95
 
96
- it "does not report #{scope} used only as namespaces" do
96
+ it "does not report #{scope} used only as a namespace" do
97
97
  src = <<-EOS
98
98
  #{scope} Alfa
99
99
  # Describes Bravo
@@ -104,10 +104,28 @@ RSpec.describe Reek::SmellDetectors::IrresponsibleModule do
104
104
  end
105
105
  EOS
106
106
 
107
- expect(src).not_to reek_of(:IrresponsibleModule)
107
+ expect(src).not_to reek_of(:IrresponsibleModule, context: 'Alfa')
108
108
  end
109
109
 
110
- it "reports #{scope} that have both a nested #{scope} and methods" do
110
+ it "does not report #{scope} used only as a namespace for several nested moduless" do
111
+ src = <<-EOS
112
+ #{scope} Alfa
113
+ # Describes Bravo
114
+ class Bravo
115
+ def charlie
116
+ end
117
+ end
118
+
119
+ # Describes Delta
120
+ module Delta
121
+ end
122
+ end
123
+ EOS
124
+
125
+ expect(src).not_to reek_of(:IrresponsibleModule, context: 'Alfa')
126
+ end
127
+
128
+ it "reports #{scope} that is used as a namespace but also has methods" do
111
129
  src = <<-EOS
112
130
  #{scope} Alfa
113
131
  def bravo
@@ -122,7 +140,7 @@ RSpec.describe Reek::SmellDetectors::IrresponsibleModule do
122
140
  expect(src).to reek_of(:IrresponsibleModule, context: 'Alfa')
123
141
  end
124
142
 
125
- it "reports #{scope} that has both a nested #{scope} and singleton methods" do
143
+ it "reports #{scope} that is used as a namespace but also has singleton methods" do
126
144
  src = <<-EOS
127
145
  #{scope} Alfa
128
146
  def self.bravo
@@ -147,10 +165,40 @@ RSpec.describe Reek::SmellDetectors::IrresponsibleModule do
147
165
  end
148
166
  EOS
149
167
 
150
- expect(src).not_to reek_of(:IrresponsibleModule)
168
+ expect(src).not_to reek_of(:IrresponsibleModule, context: 'Alfa')
169
+ end
170
+
171
+ it "does not report #{scope} only containing constants" do
172
+ src = <<-EOS
173
+ #{scope} Alfa
174
+ Bravo = 23
175
+ end
176
+ EOS
177
+
178
+ expect(src).not_to reek_of(:IrresponsibleModule, context: 'Alfa')
179
+ end
180
+
181
+ it "reports #{scope} that contains method calls" do
182
+ src = <<-EOS
183
+ #{scope} Alfa
184
+ bravo :charlie
185
+ end
186
+ EOS
187
+
188
+ expect(src).to reek_of(:IrresponsibleModule, context: 'Alfa')
189
+ end
190
+
191
+ it "reports #{scope} that contains non-constant assignments" do
192
+ src = <<-EOS
193
+ #{scope} Alfa
194
+ bravo = charlie
195
+ end
196
+ EOS
197
+
198
+ expect(src).to reek_of(:IrresponsibleModule, context: 'Alfa')
151
199
  end
152
200
 
153
- it "reports a #{scope} defined through assignment" do
201
+ it "reports an irresponsible #{scope} defined through assignment" do
154
202
  src = <<-EOS
155
203
  # Alfa is responsible, but Bravo is not
156
204
  #{scope} Alfa
@@ -174,6 +222,7 @@ RSpec.describe Reek::SmellDetectors::IrresponsibleModule do
174
222
 
175
223
  it 'does not report constants that are not classes' do
176
224
  src = <<-EOS
225
+ # Alfa is responsible
177
226
  #{scope} Alfa
178
227
  Bravo = 23
179
228
  Charlie = Hash.new
@@ -0,0 +1,17 @@
1
+ require_relative '../../spec_helper'
2
+ require_lib 'reek/smell_detectors/syntax'
3
+
4
+ RSpec.describe Reek::SmellDetectors::Syntax do
5
+ it 'reports the right values' do
6
+ src = <<-EOS
7
+ class Alfa
8
+ edn
9
+ EOS
10
+
11
+ expect(src).to reek_of(:Syntax,
12
+ lines: [3],
13
+ context: 'This file',
14
+ message: 'has unexpected token $end',
15
+ source: 'string')
16
+ end
17
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reek
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.7.0
4
+ version: 4.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Rutherford
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2017-05-31 00:00:00.000000000 Z
14
+ date: 2017-06-12 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: codeclimate-engine-rb
@@ -162,6 +162,7 @@ files:
162
162
  - features/configuration_via_source_comments/well_formed_source_comments.feature
163
163
  - features/programmatic_access.feature
164
164
  - features/rake_task/rake_task.feature
165
+ - features/reports/codeclimate.feature
165
166
  - features/reports/json.feature
166
167
  - features/reports/reports.feature
167
168
  - features/reports/yaml.feature
@@ -180,6 +181,7 @@ files:
180
181
  - lib/reek/ast/sexp_extensions.rb
181
182
  - lib/reek/ast/sexp_extensions/arguments.rb
182
183
  - lib/reek/ast/sexp_extensions/attribute_assignments.rb
184
+ - lib/reek/ast/sexp_extensions/begin.rb
183
185
  - lib/reek/ast/sexp_extensions/block.rb
184
186
  - lib/reek/ast/sexp_extensions/case.rb
185
187
  - lib/reek/ast/sexp_extensions/constant.rb
@@ -390,6 +392,7 @@ files:
390
392
  - spec/reek/smell_detectors/prima_donna_method_spec.rb
391
393
  - spec/reek/smell_detectors/repeated_conditional_spec.rb
392
394
  - spec/reek/smell_detectors/subclassed_from_core_class_spec.rb
395
+ - spec/reek/smell_detectors/syntax_spec.rb
393
396
  - spec/reek/smell_detectors/too_many_constants_spec.rb
394
397
  - spec/reek/smell_detectors/too_many_instance_variables_spec.rb
395
398
  - spec/reek/smell_detectors/too_many_methods_spec.rb