reek 4.7.0 → 4.7.1

Sign up to get free protection for your applications and to get access to all the features.
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