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