reek 4.0.5 → 4.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +24 -1
- data/defaults.reek +3 -0
- data/docs/Subclassed-From-Core-Class.md +79 -0
- data/features/samples.feature +6 -3
- data/features/smells/subclassed_from_core_class.feature +14 -0
- data/lib/reek/ast/sexp_extensions/constant.rb +4 -1
- data/lib/reek/ast/sexp_extensions/module.rb +63 -7
- data/lib/reek/report/code_climate/code_climate_configuration.yml +11 -0
- data/lib/reek/smells.rb +1 -0
- data/lib/reek/smells/subclassed_from_core_class.rb +57 -0
- data/lib/reek/version.rb +1 -1
- data/spec/reek/ast/sexp_extensions_spec.rb +105 -9
- data/spec/reek/smells/subclassed_from_core_class_spec.rb +135 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb4b9a8e91de50f96e7fc83d96e72f92af778dfc
|
4
|
+
data.tar.gz: f33a99fd21ffa8d82eb48d4e1cde69a0a01e0468
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82da2cb804ea9c2fe37665072c0666350c7a22ba0e857f3629589ca7f91e87a55ee84ae198cd01e06d51e7e00d16b1aabde1b5d7f3cd59523435118e6692d954
|
7
|
+
data.tar.gz: 5177823be762c3c3354144a57abee5c1fea661d2ed9f947e63233a16de0aeae3dd8ac443bd3beb17cb7f3d22ac1d0d12f106a8e8eab568a934b878d8bf550cdd
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -440,11 +440,34 @@ Or just run the whole test suite:
|
|
440
440
|
bundle exec rake
|
441
441
|
```
|
442
442
|
|
443
|
+
This will run the tests (RSpec and Cucumber), RuboCop and Reek itself.
|
444
|
+
|
445
|
+
You can also run:
|
446
|
+
|
447
|
+
```
|
448
|
+
bundle exec rake ci
|
449
|
+
```
|
450
|
+
|
451
|
+
This will run everything the default task runs and also [Ataru](https://github.com/CodePadawans/ataru) and [Mutant](https://github.com/mbj/mutant). This is the task that we run on Travis as well and that determines if your pull request is green or red.
|
452
|
+
|
453
|
+
Another useful Rake task is the `console` task. This will throw you right into an environment where you can play around with Reeks modules and classes:
|
454
|
+
|
455
|
+
```
|
456
|
+
bundle exec rake console
|
457
|
+
|
458
|
+
[3] pry(main)> require_relative 'lib/reek/examiner'
|
459
|
+
=> true
|
460
|
+
[4] pry(main)> Reek::Examiner
|
461
|
+
=> Reek::Examiner
|
462
|
+
```
|
463
|
+
|
464
|
+
Have a look at our [Developer API](docs/API.md) for more inspiration.
|
465
|
+
|
443
466
|
From then on you should check out:
|
467
|
+
|
444
468
|
* [How Reek works internally](docs/How-reek-works-internally.md)
|
445
469
|
* [the contributing guide](CONTRIBUTING.md)
|
446
470
|
|
447
|
-
|
448
471
|
If you don't feel like getting your hands dirty with code there are still other ways you can help us:
|
449
472
|
|
450
473
|
* Open up an [issue](https://github.com/troessner/reek/issues) and report bugs
|
data/defaults.reek
CHANGED
@@ -0,0 +1,79 @@
|
|
1
|
+
# Subclassed From Core Class
|
2
|
+
|
3
|
+
## Introduction
|
4
|
+
|
5
|
+
Candidate classes for the _Subclassed From Core Class_ smell are classes which inherit from Core Classes like Hash, String and Array.
|
6
|
+
|
7
|
+
Inheriting from Core Classes means that you are going to have a bad time debugging (the explanation below is taken from [here](http://words.steveklabnik.com/beware-subclassing-ruby-core-classes)):
|
8
|
+
|
9
|
+
> What do you think this code should do?
|
10
|
+
|
11
|
+
```Ruby
|
12
|
+
List = Class.new(Array)
|
13
|
+
|
14
|
+
l = List.new
|
15
|
+
l << 1
|
16
|
+
l << 2
|
17
|
+
puts l.reverse.class # => Array
|
18
|
+
```
|
19
|
+
|
20
|
+
> If you said “it prints Array” you’d be right.
|
21
|
+
> Let’s talk about a more pernicious issue: Strings.
|
22
|
+
|
23
|
+
```Ruby
|
24
|
+
class MyString < String
|
25
|
+
def to_s
|
26
|
+
"lol"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
s = MyString.new
|
31
|
+
s.concat "Hey"
|
32
|
+
|
33
|
+
puts s # => Hey
|
34
|
+
puts s.to_s # => lol
|
35
|
+
puts "#{s}" # => Hey
|
36
|
+
```
|
37
|
+
|
38
|
+
> That’s right! With Strings, Ruby doesn’t call #to_s: it puts the value in directly.
|
39
|
+
> Generally speaking, subclassing isn’t the right idea here.
|
40
|
+
|
41
|
+
## Example
|
42
|
+
|
43
|
+
Given
|
44
|
+
|
45
|
+
```Ruby
|
46
|
+
class Ary < Array
|
47
|
+
end
|
48
|
+
|
49
|
+
class Str < String
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
Reek would report the _Subclassed From Core Class_ smell for both classes. Instead of subclassing them you want a data structure that uses one of these core classes internally, but isn’t exactly like one. For instance:
|
54
|
+
|
55
|
+
```Ruby
|
56
|
+
require 'forwardable'
|
57
|
+
|
58
|
+
class List
|
59
|
+
extend Forwardable
|
60
|
+
def_delegators :@list, :<<, :length # and anything else
|
61
|
+
|
62
|
+
def initialize(list = [])
|
63
|
+
@list = list
|
64
|
+
end
|
65
|
+
|
66
|
+
def reverse
|
67
|
+
List.new(@list.reverse)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
l = List.new
|
72
|
+
l << 1
|
73
|
+
l << 2
|
74
|
+
puts l.reverse.class # => List
|
75
|
+
```
|
76
|
+
|
77
|
+
## Configuration
|
78
|
+
|
79
|
+
_Subclassed From Core Class_ offers the [Basic Smell Options](Basic-Smell-Options.md).
|
data/features/samples.feature
CHANGED
@@ -59,7 +59,7 @@ Feature: Basic smell detection
|
|
59
59
|
UncommunicativeVariableName: Inline::C#module_name has the variable name 'x' [https://github.com/troessner/reek/blob/master/docs/Uncommunicative-Variable-Name.md]
|
60
60
|
UncommunicativeVariableName: Inline::C#parse_signature has the variable name 'x' [https://github.com/troessner/reek/blob/master/docs/Uncommunicative-Variable-Name.md]
|
61
61
|
UtilityFunction: Inline::C#strip_comments doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/master/docs/Utility-Function.md]
|
62
|
-
optparse.rb --
|
62
|
+
optparse.rb -- 119 warnings:
|
63
63
|
Attribute: OptionParser#banner is a writable attribute [https://github.com/troessner/reek/blob/master/docs/Attribute.md]
|
64
64
|
Attribute: OptionParser#default_argv is a writable attribute [https://github.com/troessner/reek/blob/master/docs/Attribute.md]
|
65
65
|
Attribute: OptionParser#program_name is a writable attribute [https://github.com/troessner/reek/blob/master/docs/Attribute.md]
|
@@ -129,6 +129,8 @@ Feature: Basic smell detection
|
|
129
129
|
RepeatedConditional: OptionParser tests default_pattern at least 7 times [https://github.com/troessner/reek/blob/master/docs/Repeated-Conditional.md]
|
130
130
|
RepeatedConditional: OptionParser tests not_style at least 3 times [https://github.com/troessner/reek/blob/master/docs/Repeated-Conditional.md]
|
131
131
|
RepeatedConditional: OptionParser tests s at least 7 times [https://github.com/troessner/reek/blob/master/docs/Repeated-Conditional.md]
|
132
|
+
SubclassedFromCoreClass: OptionParser::CompletingHash inherits from a core class (Hash) [https://github.com/troessner/reek/blob/master/docs/Subclassed-From-Core-Class.md]
|
133
|
+
SubclassedFromCoreClass: OptionParser::OptionMap inherits from a core class (Hash) [https://github.com/troessner/reek/blob/master/docs/Subclassed-From-Core-Class.md]
|
132
134
|
TooManyInstanceVariables: OptionParser has at least 6 instance variables [https://github.com/troessner/reek/blob/master/docs/Too-Many-Instance-Variables.md]
|
133
135
|
TooManyInstanceVariables: OptionParser::Switch has at least 7 instance variables [https://github.com/troessner/reek/blob/master/docs/Too-Many-Instance-Variables.md]
|
134
136
|
TooManyMethods: OptionParser has at least 42 methods [https://github.com/troessner/reek/blob/master/docs/Too-Many-Methods.md]
|
@@ -177,7 +179,7 @@ Feature: Basic smell detection
|
|
177
179
|
UnusedParameters: OptionParser::Completion#convert has unused parameter 'opt' [https://github.com/troessner/reek/blob/master/docs/Unused-Parameters.md]
|
178
180
|
UnusedParameters: OptionParser::Switch::NoArgument#parse has unused parameter 'argv' [https://github.com/troessner/reek/blob/master/docs/Unused-Parameters.md]
|
179
181
|
UnusedParameters: OptionParser::Switch::OptionalArgument#parse has unused parameter 'argv' [https://github.com/troessner/reek/blob/master/docs/Unused-Parameters.md]
|
180
|
-
redcloth.rb --
|
182
|
+
redcloth.rb -- 103 warnings:
|
181
183
|
Attribute: RedCloth#filter_html is a writable attribute [https://github.com/troessner/reek/blob/master/docs/Attribute.md]
|
182
184
|
Attribute: RedCloth#filter_styles is a writable attribute [https://github.com/troessner/reek/blob/master/docs/Attribute.md]
|
183
185
|
Attribute: RedCloth#hard_breaks is a writable attribute [https://github.com/troessner/reek/blob/master/docs/Attribute.md]
|
@@ -226,6 +228,7 @@ Feature: Basic smell detection
|
|
226
228
|
RepeatedConditional: RedCloth tests codepre.zero? at least 3 times [https://github.com/troessner/reek/blob/master/docs/Repeated-Conditional.md]
|
227
229
|
RepeatedConditional: RedCloth tests href at least 3 times [https://github.com/troessner/reek/blob/master/docs/Repeated-Conditional.md]
|
228
230
|
RepeatedConditional: RedCloth tests title at least 4 times [https://github.com/troessner/reek/blob/master/docs/Repeated-Conditional.md]
|
231
|
+
SubclassedFromCoreClass: RedCloth inherits from a core class (String) [https://github.com/troessner/reek/blob/master/docs/Subclassed-From-Core-Class.md]
|
229
232
|
TooManyMethods: RedCloth has at least 44 methods [https://github.com/troessner/reek/blob/master/docs/Too-Many-Methods.md]
|
230
233
|
TooManyStatements: RedCloth#block_markdown_bq has approx 6 statements [https://github.com/troessner/reek/blob/master/docs/Too-Many-Statements.md]
|
231
234
|
TooManyStatements: RedCloth#block_textile_lists has approx 21 statements [https://github.com/troessner/reek/blob/master/docs/Too-Many-Statements.md]
|
@@ -280,5 +283,5 @@ Feature: Basic smell detection
|
|
280
283
|
UtilityFunction: RedCloth#lT doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/master/docs/Utility-Function.md]
|
281
284
|
UtilityFunction: RedCloth#no_textile doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/master/docs/Utility-Function.md]
|
282
285
|
UtilityFunction: RedCloth#v_align doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/master/docs/Utility-Function.md]
|
283
|
-
|
286
|
+
269 total warnings
|
284
287
|
"""
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Feature: Smell - SubclassedFromCoreClass
|
2
|
+
Scenario: Reports smell
|
3
|
+
Given a file named "my_hash.rb" with:
|
4
|
+
"""
|
5
|
+
# The MyHash class
|
6
|
+
class MyHash < Hash
|
7
|
+
end
|
8
|
+
"""
|
9
|
+
When I run `reek my_hash.rb`
|
10
|
+
Then it reports:
|
11
|
+
"""
|
12
|
+
my_hash.rb -- 1 warning:
|
13
|
+
[2]:SubclassedFromCoreClass: MyHash inherits from a core class (Hash) [https://github.com/troessner/reek/blob/master/docs/Subclassed-From-Core-Class.md]
|
14
|
+
"""
|
@@ -5,7 +5,6 @@ module Reek
|
|
5
5
|
# Utility methods for :const nodes.
|
6
6
|
module ConstNode
|
7
7
|
def name
|
8
|
-
namespace = children.first
|
9
8
|
if namespace
|
10
9
|
"#{namespace.format_to_ruby}::#{simple_name}"
|
11
10
|
else
|
@@ -16,6 +15,10 @@ module Reek
|
|
16
15
|
def simple_name
|
17
16
|
children.last
|
18
17
|
end
|
18
|
+
|
19
|
+
def namespace
|
20
|
+
children.first
|
21
|
+
end
|
19
22
|
end
|
20
23
|
end
|
21
24
|
end
|
@@ -45,6 +45,7 @@ module Reek
|
|
45
45
|
# Utility methods for :class nodes.
|
46
46
|
module ClassNode
|
47
47
|
include ModuleNodeBase
|
48
|
+
|
48
49
|
def superclass() children[1] end
|
49
50
|
end
|
50
51
|
|
@@ -53,16 +54,71 @@ module Reek
|
|
53
54
|
include ModuleNodeBase
|
54
55
|
|
55
56
|
def defines_module?
|
56
|
-
|
57
|
-
call = case value.type
|
58
|
-
when :block
|
59
|
-
value.call
|
60
|
-
when :send
|
61
|
-
value
|
62
|
-
end
|
57
|
+
call = constant_definition
|
63
58
|
call && call.module_creation_call?
|
64
59
|
end
|
65
60
|
|
61
|
+
# This is the right hand side of a constant
|
62
|
+
# assignment.
|
63
|
+
#
|
64
|
+
# This can be simple:
|
65
|
+
#
|
66
|
+
# Foo = 23
|
67
|
+
#
|
68
|
+
# s(:casgn, nil, :Foo,
|
69
|
+
# s(:int, 23))
|
70
|
+
#
|
71
|
+
# In this cases we do not care and return nil.
|
72
|
+
#
|
73
|
+
# Or complicated:
|
74
|
+
#
|
75
|
+
# Iterator = Struct.new :exp do ... end
|
76
|
+
#
|
77
|
+
# s(:casgn, nil, :Iterator,
|
78
|
+
# s(:block,
|
79
|
+
# s(:send,
|
80
|
+
# s(:const, nil, :Struct), :new,
|
81
|
+
# s(:sym, :exp)
|
82
|
+
# ),
|
83
|
+
# s(:args),
|
84
|
+
# ...
|
85
|
+
# )
|
86
|
+
# )
|
87
|
+
#
|
88
|
+
# In this cases we return the Struct.new part
|
89
|
+
#
|
90
|
+
def constant_definition
|
91
|
+
return nil unless value
|
92
|
+
|
93
|
+
case value.type
|
94
|
+
when :block
|
95
|
+
value.call
|
96
|
+
when :send
|
97
|
+
value
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Sometimes we assign classes like:
|
102
|
+
#
|
103
|
+
# Foo = Class.new(Bar)
|
104
|
+
#
|
105
|
+
# This is mapped into the following expression:
|
106
|
+
#
|
107
|
+
# s(:casgn, nil :Foo,
|
108
|
+
# s(:send,
|
109
|
+
# s(:const, nil, :Class), :new,
|
110
|
+
# s(:const, nil, :Bar)
|
111
|
+
# )
|
112
|
+
# )
|
113
|
+
#
|
114
|
+
# And we are only looking for s(:const, nil, :Bar)
|
115
|
+
#
|
116
|
+
def superclass
|
117
|
+
return nil unless constant_definition
|
118
|
+
|
119
|
+
constant_definition.args.first
|
120
|
+
end
|
121
|
+
|
66
122
|
def name
|
67
123
|
children[1].to_s
|
68
124
|
end
|
@@ -662,3 +662,14 @@ UtilityFunction:
|
|
662
662
|
remediation_points: 250_000
|
663
663
|
content: |
|
664
664
|
A _Utility Function_ is any instance method that has no dependency on the state of the instance.
|
665
|
+
SubclassedFromCoreClass:
|
666
|
+
remediation_points: 250_000
|
667
|
+
content: |
|
668
|
+
Subclassing core classes in Ruby can lead to unexpected side effects.
|
669
|
+
|
670
|
+
Knowing that Ruby has a core library, which is written in C,
|
671
|
+
and a standard library, which is written in Ruby,
|
672
|
+
if you do not know exactly how these core classes operate at the C level,
|
673
|
+
you are gonna have a bad time.
|
674
|
+
|
675
|
+
Source: http://words.steveklabnik.com/beware-subclassing-ruby-core-classes
|
data/lib/reek/smells.rb
CHANGED
@@ -14,6 +14,7 @@ require_relative 'smells/nested_iterators'
|
|
14
14
|
require_relative 'smells/nil_check'
|
15
15
|
require_relative 'smells/prima_donna_method'
|
16
16
|
require_relative 'smells/repeated_conditional'
|
17
|
+
require_relative 'smells/subclassed_from_core_class'
|
17
18
|
require_relative 'smells/too_many_instance_variables'
|
18
19
|
require_relative 'smells/too_many_methods'
|
19
20
|
require_relative 'smells/too_many_statements'
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'smell_detector'
|
3
|
+
require_relative 'smell_warning'
|
4
|
+
|
5
|
+
module Reek
|
6
|
+
module Smells
|
7
|
+
#
|
8
|
+
# Subclassing core classes in Ruby can lead to unexpected side effects.
|
9
|
+
# Knowing that Ruby has a core library, which is written in C, and a standard
|
10
|
+
# library, which is written in Ruby, if you do not know exactly how these core
|
11
|
+
# classes operate at the C level, you are gonna have a bad time.
|
12
|
+
#
|
13
|
+
# Source: http://words.steveklabnik.com/beware-subclassing-ruby-core-classes
|
14
|
+
#
|
15
|
+
class SubclassedFromCoreClass < SmellDetector
|
16
|
+
CORE_CLASSES = ['Array', 'Hash', 'String'].freeze
|
17
|
+
|
18
|
+
def self.contexts
|
19
|
+
[:class, :casgn]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Checks +ctx+ for either expressions:
|
23
|
+
#
|
24
|
+
# Foo = Class.new(Bar)
|
25
|
+
#
|
26
|
+
# class Foo < Bar; end;
|
27
|
+
#
|
28
|
+
# @return [Array<SmellWarning>]
|
29
|
+
def inspect(ctx)
|
30
|
+
superclass = ctx.exp.superclass
|
31
|
+
|
32
|
+
return [] unless superclass
|
33
|
+
|
34
|
+
inspect_superclass(ctx, superclass.name)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def inspect_superclass(ctx, superclass_name)
|
40
|
+
return [] unless CORE_CLASSES.include?(superclass_name)
|
41
|
+
|
42
|
+
[build_smell_warning(ctx, superclass_name)]
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_smell_warning(ctx, ancestor_name)
|
46
|
+
smell_attributes = {
|
47
|
+
context: ctx,
|
48
|
+
lines: [ctx.exp.line],
|
49
|
+
message: "inherits from a core class (#{ancestor_name})",
|
50
|
+
parameters: { ancestor: ancestor_name }
|
51
|
+
}
|
52
|
+
|
53
|
+
smell_warning(smell_attributes)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/reek/version.rb
CHANGED
@@ -240,6 +240,44 @@ RSpec.describe Reek::AST::SexpExtensions::LvarNode do
|
|
240
240
|
end
|
241
241
|
end
|
242
242
|
|
243
|
+
RSpec.describe Reek::AST::SexpExtensions::ConstNode do
|
244
|
+
describe '#name' do
|
245
|
+
it 'returns the fully qualified name' do
|
246
|
+
node = sexp(:const, sexp(:const, sexp(:cbase), :Parser), :AST)
|
247
|
+
|
248
|
+
expect(node.name).to eq "#{sexp(:const, sexp(:cbase), :Parser)}::AST"
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'returns only the name in case of no namespace' do
|
252
|
+
node = sexp(:const, nil, :AST)
|
253
|
+
|
254
|
+
expect(node.name).to eq 'AST'
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
describe '#simple_name' do
|
259
|
+
it 'returns the name' do
|
260
|
+
node = sexp(:const, sexp(:const, nil, :Rake), :TaskLib)
|
261
|
+
|
262
|
+
expect(node.simple_name).to eq :TaskLib
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
describe '#namespace' do
|
267
|
+
it 'returns the namespace' do
|
268
|
+
node = sexp(:const, :Parser, :AST)
|
269
|
+
|
270
|
+
expect(node.namespace).to eq :Parser
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'returns nil in case of no namespace' do
|
274
|
+
node = sexp(:const, nil, :AST)
|
275
|
+
|
276
|
+
expect(node.namespace).to be_nil
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
243
281
|
RSpec.describe Reek::AST::SexpExtensions::SendNode do
|
244
282
|
context 'with no parameters' do
|
245
283
|
let(:node) { sexp(:send, nil, :hello) }
|
@@ -403,20 +441,78 @@ RSpec.describe Reek::AST::SexpExtensions::ModuleNode do
|
|
403
441
|
end
|
404
442
|
|
405
443
|
RSpec.describe Reek::AST::SexpExtensions::CasgnNode do
|
406
|
-
context '
|
407
|
-
|
408
|
-
|
444
|
+
context '#defines_module' do
|
445
|
+
context 'with single assignment' do
|
446
|
+
subject do
|
447
|
+
sexp(:casgn, nil, :Foo)
|
448
|
+
end
|
449
|
+
|
450
|
+
it 'does not define a module' do
|
451
|
+
expect(subject.defines_module?).to be_falsey
|
452
|
+
end
|
409
453
|
end
|
410
454
|
|
411
|
-
|
412
|
-
|
455
|
+
context 'with implicit receiver to new' do
|
456
|
+
it 'does not define a module' do
|
457
|
+
exp = sexp(:casgn, nil, :Foo, sexp(:send, nil, :new))
|
458
|
+
expect(exp.defines_module?).to be_falsey
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
context 'with implicit receiver to new' do
|
463
|
+
it 'does not define a module' do
|
464
|
+
exp = Reek::Source::SourceCode.from('Foo = Class.new(Bar)').syntax_tree
|
465
|
+
|
466
|
+
expect(exp.defines_module?).to be_truthy
|
467
|
+
end
|
413
468
|
end
|
414
469
|
end
|
415
470
|
|
416
|
-
context '
|
417
|
-
it '
|
418
|
-
exp =
|
419
|
-
|
471
|
+
context '#superclass' do
|
472
|
+
it 'returns the superclass from the class definition' do
|
473
|
+
exp = Reek::Source::SourceCode.from('Foo = Class.new(Bar)').syntax_tree
|
474
|
+
|
475
|
+
expect(exp.superclass).to eq sexp(:const, nil, :Bar)
|
476
|
+
end
|
477
|
+
|
478
|
+
it 'returns nil in case of no class definition' do
|
479
|
+
exp = Reek::Source::SourceCode.from('Foo = 23').syntax_tree
|
480
|
+
|
481
|
+
expect(exp.superclass).to be_nil
|
482
|
+
end
|
483
|
+
|
484
|
+
it 'returns nil in case of no superclass' do
|
485
|
+
exp = Reek::Source::SourceCode.from('Foo = Class.new').syntax_tree
|
486
|
+
|
487
|
+
expect(exp.superclass).to be_nil
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
context '#constant_definition' do
|
492
|
+
context 'simple constant assignment' do
|
493
|
+
it 'is not important' do
|
494
|
+
exp = sexp(:casgn, nil, :Foo, sexp(:int, 23))
|
495
|
+
|
496
|
+
expect(exp.constant_definition).to be_nil
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
context 'complicated constant assignments' do
|
501
|
+
it 'calls the block and returns what is being sent' do
|
502
|
+
exp = Reek::Source::SourceCode.from('Iterator = Struct.new(:exp) { def p; end; }').syntax_tree
|
503
|
+
|
504
|
+
struct_exp = sexp(:send, sexp(:const, nil, :Struct), :new, sexp(:sym, :exp))
|
505
|
+
|
506
|
+
expect(exp.constant_definition).to eq struct_exp
|
507
|
+
end
|
508
|
+
|
509
|
+
it 'returns the expression responsible for class creation' do
|
510
|
+
exp = Reek::Source::SourceCode.from('Foo = Class.new(Bar)').syntax_tree
|
511
|
+
|
512
|
+
class_exp = sexp(:send, sexp(:const, nil, :Class), :new, sexp(:const, nil, :Bar))
|
513
|
+
|
514
|
+
expect(exp.constant_definition).to eq class_exp
|
515
|
+
end
|
420
516
|
end
|
421
517
|
end
|
422
518
|
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
require_lib 'reek/smells/subclassed_from_core_class'
|
3
|
+
require_relative 'smell_detector_shared'
|
4
|
+
|
5
|
+
RSpec.describe Reek::Smells::SubclassedFromCoreClass do
|
6
|
+
let(:detector) { described_class.new }
|
7
|
+
|
8
|
+
it_should_behave_like 'SmellDetector'
|
9
|
+
|
10
|
+
context 'report' do
|
11
|
+
context 'smell line' do
|
12
|
+
context 'single class' do
|
13
|
+
it 'should report the core class in the message' do
|
14
|
+
src = <<-EOS
|
15
|
+
class Dummy < Hash
|
16
|
+
end
|
17
|
+
EOS
|
18
|
+
|
19
|
+
expect(src).to reek_of(:SubclassedFromCoreClass, lines: [1])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'class inside a module' do
|
24
|
+
it 'should report the core class in the message' do
|
25
|
+
src = <<-EOS
|
26
|
+
module Namespace
|
27
|
+
class Dummy < Hash
|
28
|
+
end
|
29
|
+
end
|
30
|
+
EOS
|
31
|
+
|
32
|
+
expect(src).to reek_of(:SubclassedFromCoreClass, lines: [2])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'smell message' do
|
38
|
+
context 'Array' do
|
39
|
+
it 'should report the core class in the message' do
|
40
|
+
src = <<-EOS
|
41
|
+
class Dummy < Array
|
42
|
+
end
|
43
|
+
EOS
|
44
|
+
|
45
|
+
expect(src).to reek_of(:SubclassedFromCoreClass, message: 'inherits from a core class (Array)')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'Hash' do
|
50
|
+
it 'should report the core class in the message' do
|
51
|
+
src = <<-EOS
|
52
|
+
class Dummy < Hash
|
53
|
+
end
|
54
|
+
EOS
|
55
|
+
|
56
|
+
expect(src).to reek_of(:SubclassedFromCoreClass, message: 'inherits from a core class (Hash)')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'does not inherit from a core class' do
|
63
|
+
src = <<-EOS
|
64
|
+
class Dummy
|
65
|
+
end
|
66
|
+
EOS
|
67
|
+
|
68
|
+
expect(src).to_not reek_of(:SubclassedFromCoreClass)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should report if we inherit from a core class' do
|
72
|
+
src = <<-EOS
|
73
|
+
class Dummy < Array
|
74
|
+
end
|
75
|
+
EOS
|
76
|
+
|
77
|
+
expect(src).to reek_of(:SubclassedFromCoreClass, ancestor: 'Array', message: 'inherits from a core class (Array)')
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should not report on coincidental core class names in other namespaces' do
|
81
|
+
src = <<-EOS
|
82
|
+
class Dummy < My::Array
|
83
|
+
end
|
84
|
+
EOS
|
85
|
+
expect(src).to_not reek_of(:SubclassedFromCoreClass)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should report if we inherit from a core class from within a namespaced class' do
|
89
|
+
src = <<-EOS
|
90
|
+
module Namespace
|
91
|
+
class Dummy < Array
|
92
|
+
end
|
93
|
+
end
|
94
|
+
EOS
|
95
|
+
expect(src).to reek_of(:SubclassedFromCoreClass, ancestor: 'Array')
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should report if we inherit from a core class using Class#new' do
|
99
|
+
src = 'Dummy = Class.new(Array)'
|
100
|
+
expect(src).to reek_of(:SubclassedFromCoreClass, ancestor: 'Array')
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should report if inner class inherit from a core class' do
|
104
|
+
src = <<-EOS
|
105
|
+
module Namespace
|
106
|
+
class Dummy
|
107
|
+
Dummiest = Class.new(Array)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
EOS
|
111
|
+
expect(src).to reek_of(:SubclassedFromCoreClass, ancestor: 'Array')
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should not report on coincidental core class names in other namespaces' do
|
115
|
+
src = <<-EOS
|
116
|
+
module Namespace
|
117
|
+
class Dummy
|
118
|
+
Dummiest = Class.new(My::Array)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
EOS
|
122
|
+
expect(src).to_not reek_of(:SubclassedFromCoreClass, ancestor: 'Array')
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should not report if inner class inherits from allowed classes' do
|
126
|
+
src = <<-EOS
|
127
|
+
module Namespace
|
128
|
+
class Dummy
|
129
|
+
Dummiest = Class.new(StandardError)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
EOS
|
133
|
+
expect(src).to_not reek_of(:SubclassedFromCoreClass, ancestor: 'StandardError')
|
134
|
+
end
|
135
|
+
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.0
|
4
|
+
version: 4.1.0
|
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: 2016-06-
|
14
|
+
date: 2016-06-23 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: codeclimate-engine-rb
|
@@ -118,6 +118,7 @@ files:
|
|
118
118
|
- docs/Repeated-Conditional.md
|
119
119
|
- docs/Simulated-Polymorphism.md
|
120
120
|
- docs/Smell-Suppression.md
|
121
|
+
- docs/Subclassed-From-Core-Class.md
|
121
122
|
- docs/Too-Many-Instance-Variables.md
|
122
123
|
- docs/Too-Many-Methods.md
|
123
124
|
- docs/Too-Many-Statements.md
|
@@ -156,6 +157,7 @@ files:
|
|
156
157
|
- features/reports/reports.feature
|
157
158
|
- features/reports/yaml.feature
|
158
159
|
- features/samples.feature
|
160
|
+
- features/smells/subclassed_from_core_class.feature
|
159
161
|
- features/step_definitions/.rubocop.yml
|
160
162
|
- features/step_definitions/reek_steps.rb
|
161
163
|
- features/step_definitions/sample_file_steps.rb
|
@@ -243,6 +245,7 @@ files:
|
|
243
245
|
- lib/reek/smells/smell_detector.rb
|
244
246
|
- lib/reek/smells/smell_repository.rb
|
245
247
|
- lib/reek/smells/smell_warning.rb
|
248
|
+
- lib/reek/smells/subclassed_from_core_class.rb
|
246
249
|
- lib/reek/smells/too_many_instance_variables.rb
|
247
250
|
- lib/reek/smells/too_many_methods.rb
|
248
251
|
- lib/reek/smells/too_many_statements.rb
|
@@ -322,6 +325,7 @@ files:
|
|
322
325
|
- spec/reek/smells/smell_detector_spec.rb
|
323
326
|
- spec/reek/smells/smell_repository_spec.rb
|
324
327
|
- spec/reek/smells/smell_warning_spec.rb
|
328
|
+
- spec/reek/smells/subclassed_from_core_class_spec.rb
|
325
329
|
- spec/reek/smells/too_many_instance_variables_spec.rb
|
326
330
|
- spec/reek/smells/too_many_methods_spec.rb
|
327
331
|
- spec/reek/smells/too_many_statements_spec.rb
|