pelusa 0.2.2 → 0.2.3
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 +15 -0
- data/lib/pelusa.rb +0 -2
- data/lib/pelusa/analyzer.rb +11 -4
- data/lib/pelusa/class_analyzer.rb +16 -0
- data/lib/pelusa/lint/case_statements.rb +1 -3
- data/lib/pelusa/lint/collection_wrappers.rb +21 -14
- data/lib/pelusa/lint/demeter_law.rb +31 -5
- data/lib/pelusa/lint/else_clauses.rb +1 -3
- data/lib/pelusa/lint/eval_usage.rb +1 -3
- data/lib/pelusa/lint/indentation_level.rb +20 -12
- data/lib/pelusa/lint/instance_variables.rb +1 -3
- data/lib/pelusa/lint/line_restriction.rb +1 -3
- data/lib/pelusa/lint/long_identifiers.rb +2 -5
- data/lib/pelusa/lint/many_arguments.rb +2 -3
- data/lib/pelusa/lint/properties.rb +1 -3
- data/lib/pelusa/lint/short_identifiers.rb +2 -5
- data/lib/pelusa/report.rb +5 -9
- data/lib/pelusa/reporters/stdout_reporter.rb +1 -1
- data/lib/pelusa/version.rb +1 -1
- data/test/pelusa/analyzer_test.rb +48 -18
- data/test/pelusa/class_analyzer_test.rb +14 -0
- data/test/pelusa/lint/collection_wrappers_test.rb +45 -0
- data/test/pelusa/lint/demeter_law_test.rb +73 -17
- data/test/pelusa/reporters/ruby_reporter_test.rb +2 -2
- data/test/test_helper.rb +1 -1
- metadata +16 -19
- data/lib/pelusa/iterator.rb +0 -39
- data/test/pelusa/iterator_test.rb +0 -28
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YjA3NTU1ZWZkNGViYTBmNTFhMmJmNGQ1MDZlNjQxNjcwYTJkMTA1ZQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NzEzMmJlNmE5N2ZhYTE3MjgyODMzMDgxMmEyMjYxMmFhYmZlZjBjOA==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MjFmZmQyNzRiZWI3YjU0ZjE1MTNkNjViMmE5YTFiMzEwMjhiYWU1MGNiMWVi
|
10
|
+
YTI1ZDhiMjkwZjYyNjBhNGJhMThlOGQ0ZjE4M2FmMDBhZjhmNWQwMjRiNTBk
|
11
|
+
ZDgwOGQ5NzAzZmFiNTdkOTMwMTBmNmNhMWUwMDc4ZjIyMmI0OTM=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ODI4ZGVkOWE5MTgzMzdiOWYyNmNmYzMxZTNkOTRhY2E4M2FjNGQyOTRlYTg4
|
14
|
+
NDhmOTVkMWI0OWJkZTlmNjk3MjViNWEzMmUxYTRhMzMxNzQ5ZmY4ZWNjYTk5
|
15
|
+
OWQxOGUzZjFjZTQyNmI1OWZkODE3YjRkODUwNDkzMzgzY2RjY2I=
|
data/lib/pelusa.rb
CHANGED
data/lib/pelusa/analyzer.rb
CHANGED
@@ -19,9 +19,10 @@ module Pelusa
|
|
19
19
|
reports = extract_classes(ast).map do |klass|
|
20
20
|
class_analyzer = ClassAnalyzer.new(klass)
|
21
21
|
class_name = class_analyzer.class_name
|
22
|
+
type = class_analyzer.type
|
22
23
|
analysis = class_analyzer.analyze(@lints)
|
23
24
|
|
24
|
-
Report.new(class_name, analysis)
|
25
|
+
Report.new(class_name, type, analysis)
|
25
26
|
end
|
26
27
|
@reporter.reports = reports
|
27
28
|
@reporter
|
@@ -38,10 +39,16 @@ module Pelusa
|
|
38
39
|
# Returns an Array of Class nodes.
|
39
40
|
def extract_classes(ast)
|
40
41
|
classes = []
|
41
|
-
|
42
|
-
classes <<
|
42
|
+
if ast.is_a?(Rubinius::AST::Class) || ast.is_a?(Rubinius::AST::Module)
|
43
|
+
classes << ast
|
44
|
+
end
|
45
|
+
|
46
|
+
ast.walk do |continue, node|
|
47
|
+
if node.is_a?(Rubinius::AST::Class) || node.is_a?(Rubinius::AST::Module)
|
48
|
+
classes << node
|
49
|
+
end
|
50
|
+
true
|
43
51
|
end
|
44
|
-
Array(ast).each(&class_iterator)
|
45
52
|
classes
|
46
53
|
end
|
47
54
|
end
|
@@ -14,6 +14,11 @@ class Pelusa::ClassAnalyzer
|
|
14
14
|
name.name
|
15
15
|
end
|
16
16
|
|
17
|
+
# Public: Returns the type of container being examined (class or module).
|
18
|
+
def type
|
19
|
+
@klass.is_a?(Rubinius::AST::Class) ? "class" : "module"
|
20
|
+
end
|
21
|
+
|
17
22
|
# Public: Analyzes a class with a series of lints.
|
18
23
|
#
|
19
24
|
# lints - The lints to check for.
|
@@ -25,4 +30,15 @@ class Pelusa::ClassAnalyzer
|
|
25
30
|
lint.check(@klass)
|
26
31
|
end
|
27
32
|
end
|
33
|
+
|
34
|
+
# Public: Walk a node, analyzing it as it goes.
|
35
|
+
#
|
36
|
+
# block - supply a block that will be executed as the node gets walked.
|
37
|
+
def self.walk(start_node)
|
38
|
+
raise ArgumentError, "Walk requires a block!" unless block_given?
|
39
|
+
start_node.walk do |continue, node|
|
40
|
+
yield(node)
|
41
|
+
true
|
42
|
+
end
|
43
|
+
end
|
28
44
|
end
|
@@ -6,7 +6,6 @@ module Pelusa
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def check(klass)
|
9
|
-
initialize
|
10
9
|
iterate_lines!(klass)
|
11
10
|
|
12
11
|
return SuccessfulAnalysis.new(name) if @violations.empty?
|
@@ -23,12 +22,11 @@ module Pelusa
|
|
23
22
|
end
|
24
23
|
|
25
24
|
def iterate_lines!(klass)
|
26
|
-
|
25
|
+
ClassAnalyzer.walk(klass) do |node|
|
27
26
|
if node.is_a?(Rubinius::AST::Case)
|
28
27
|
@violations << node.line
|
29
28
|
end
|
30
29
|
end
|
31
|
-
Array(klass).each(&iterator)
|
32
30
|
end
|
33
31
|
end
|
34
32
|
end
|
@@ -6,7 +6,6 @@ module Pelusa
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def check(klass)
|
9
|
-
initialize
|
10
9
|
iterate_lines!(klass)
|
11
10
|
|
12
11
|
return SuccessfulAnalysis.new(name) if @violations.empty?
|
@@ -23,27 +22,35 @@ module Pelusa
|
|
23
22
|
end
|
24
23
|
|
25
24
|
def iterate_lines!(klass)
|
26
|
-
|
25
|
+
# the array of actual assignment nodes -- we only want to assign once
|
26
|
+
array_assignments = {}
|
27
|
+
# the name of all the assigned arrays, no others allowed
|
28
|
+
array_values = {}
|
27
29
|
|
28
|
-
|
30
|
+
ClassAnalyzer.walk(klass) do |node|
|
29
31
|
if node.is_a?(Rubinius::AST::InstanceVariableAssignment) &&
|
30
32
|
(node.value.is_a?(Rubinius::AST::ArrayLiteral) ||
|
31
|
-
|
32
|
-
|
33
|
+
node.value.is_a?(Rubinius::AST::EmptyArray))
|
34
|
+
array_assignments[node] = true
|
35
|
+
array_values[node.name] = true
|
33
36
|
end
|
34
37
|
end
|
35
38
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
unless array_assignments.empty?
|
40
|
+
ClassAnalyzer.walk(klass) do |node|
|
41
|
+
# if this is where we assign the node for the first time, good
|
42
|
+
unless array_assignments[node]
|
43
|
+
# otherwise, if it's an instance variable assignment, verboten!
|
44
|
+
if node.is_a?(Rubinius::AST::InstanceVariableAssignment)
|
45
|
+
@violations << node.line
|
46
|
+
# or if we access any other ivars
|
47
|
+
elsif node.is_a?(Rubinius::AST::InstanceVariableAccess) &&
|
48
|
+
!array_values[node.name]
|
49
|
+
@violations << node.line
|
50
|
+
end
|
51
|
+
end
|
43
52
|
end
|
44
53
|
end
|
45
|
-
Array(klass).each(&get_array_assignment)
|
46
|
-
Array(klass).each(&iterator)
|
47
54
|
end
|
48
55
|
end
|
49
56
|
end
|
@@ -6,7 +6,6 @@ module Pelusa
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def check(klass)
|
9
|
-
initialize
|
10
9
|
iterate_lines!(klass)
|
11
10
|
|
12
11
|
return SuccessfulAnalysis.new(name) if @violations.empty?
|
@@ -23,17 +22,44 @@ module Pelusa
|
|
23
22
|
end
|
24
23
|
|
25
24
|
def iterate_lines!(klass)
|
26
|
-
|
25
|
+
ClassAnalyzer.walk(klass) do |node|
|
27
26
|
if node.is_a?(Rubinius::AST::Send) && node.receiver.is_a?(Rubinius::AST::Send)
|
28
27
|
@violations << node.line unless white_listed?(node.receiver.name)
|
29
28
|
end
|
30
29
|
end
|
31
|
-
|
30
|
+
end
|
31
|
+
|
32
|
+
# Internal: Default modules whose methods are whitelisted.
|
33
|
+
DEFAULT_WHITELIST = [Class, Fixnum, Enumerable]
|
34
|
+
|
35
|
+
# Internal: Methods on these common, fundamental classes and modules are
|
36
|
+
# allowed to be called on other objects.
|
37
|
+
#
|
38
|
+
# Note: this doesn't currently work with namespaced objects, but would be
|
39
|
+
# easy to extend.
|
40
|
+
#
|
41
|
+
# Returns an array of classes specified in .pelusa.yml (a comma-separated
|
42
|
+
# list of class names) or the default values above.
|
43
|
+
def whitelist_sources
|
44
|
+
if whitelist = Pelusa.configuration['DemeterLaw']['whitelist']
|
45
|
+
whitelist.split(",").map {|k| Kernel.const_get(k.strip)}
|
46
|
+
else
|
47
|
+
DEFAULT_WHITELIST
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Internal: Allow conversion methods -- matching /^(to_|as_)/ to violate
|
52
|
+
# the law of Demeter.
|
53
|
+
def allow_conversions?
|
54
|
+
Pelusa.configuration['DemeterLaw'].fetch('allow_conversions', false)
|
32
55
|
end
|
33
56
|
|
34
57
|
def white_listed? method
|
35
|
-
|
36
|
-
enclosing_module.instance_methods.any?
|
58
|
+
whitelist_sources.any? do |enclosing_module|
|
59
|
+
enclosing_module.instance_methods.any? do |instance_method|
|
60
|
+
instance_method == method or
|
61
|
+
allow_conversions? && instance_method =~ /^(to_|as_)/
|
62
|
+
end
|
37
63
|
end
|
38
64
|
end
|
39
65
|
end
|
@@ -6,7 +6,6 @@ module Pelusa
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def check(klass)
|
9
|
-
initialize
|
10
9
|
iterate_lines!(klass)
|
11
10
|
|
12
11
|
return SuccessfulAnalysis.new(name) if @violations.empty?
|
@@ -23,7 +22,7 @@ module Pelusa
|
|
23
22
|
end
|
24
23
|
|
25
24
|
def iterate_lines!(klass)
|
26
|
-
|
25
|
+
ClassAnalyzer.walk(klass) do |node|
|
27
26
|
if node.is_a?(Rubinius::AST::If)
|
28
27
|
has_body = node.body && !node.body.is_a?(Rubinius::AST::NilLiteral)
|
29
28
|
has_else = node.else && !node.else.is_a?(Rubinius::AST::NilLiteral)
|
@@ -33,7 +32,6 @@ module Pelusa
|
|
33
32
|
end
|
34
33
|
end
|
35
34
|
end
|
36
|
-
Array(klass).each(&iterator)
|
37
35
|
end
|
38
36
|
end
|
39
37
|
end
|
@@ -3,7 +3,6 @@
|
|
3
3
|
module Pelusa
|
4
4
|
module Lint
|
5
5
|
class EvalUsage
|
6
|
-
|
7
6
|
def initialize
|
8
7
|
@violations = Set.new
|
9
8
|
end
|
@@ -27,10 +26,9 @@ module Pelusa
|
|
27
26
|
end
|
28
27
|
|
29
28
|
def iterate_lines!(klass)
|
30
|
-
|
29
|
+
ClassAnalyzer.walk(klass) do |node|
|
31
30
|
@violations << node.line if eval_violation?(node)
|
32
31
|
end
|
33
|
-
Array(klass).each(&iterator)
|
34
32
|
end
|
35
33
|
|
36
34
|
def eval_violation?(node)
|
@@ -6,7 +6,6 @@ module Pelusa
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def check(klass)
|
9
|
-
initialize
|
10
9
|
iterate_lines!(klass)
|
11
10
|
|
12
11
|
return SuccessfulAnalysis.new(name) if @violations.empty?
|
@@ -23,24 +22,33 @@ module Pelusa
|
|
23
22
|
end
|
24
23
|
|
25
24
|
def iterate_lines!(klass)
|
26
|
-
|
25
|
+
# we want to find all nodes inside define blocks that
|
26
|
+
# contain > 1 indentation levels
|
27
|
+
# this method totally fails the IndentationLevel level lint :P
|
28
|
+
ClassAnalyzer.walk(klass) do |node|
|
27
29
|
if node.is_a?(Rubinius::AST::Define)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
# we're inside a method body, so see if we indent anywhere
|
31
|
+
ClassAnalyzer.walk(node) do |inner_node|
|
32
|
+
if inner_body = get_body_from_node[inner_node]
|
33
|
+
# if it turns out there's an indented value in there, that
|
34
|
+
# could be okay -- walk that node to see if there's a 2nd level
|
35
|
+
if inner_node.line != [inner_body].flatten.first.line
|
36
|
+
# walk the third level, see if there's another indent
|
37
|
+
ClassAnalyzer.walk(inner_node) do |innermost_node|
|
38
|
+
if innermost_body = get_body_from_node[innermost_node]
|
39
|
+
if innermost_node.line != [innermost_body].flatten.first.line
|
40
|
+
# there's yet another level of indent -- violation!
|
41
|
+
# note: this also catches bad outdents, possibly should
|
42
|
+
# flag those separately
|
43
|
+
@violations.merge Array(innermost_body).map(&:line)
|
44
|
+
end
|
45
|
+
end
|
33
46
|
end
|
34
47
|
end
|
35
48
|
end
|
36
|
-
|
37
|
-
Array(get_body_from_node[node]).each(&__iterate)
|
38
49
|
end
|
39
|
-
node.body.array.each(&_iterate)
|
40
50
|
end
|
41
51
|
end
|
42
|
-
|
43
|
-
Array(klass).each(&iterator)
|
44
52
|
end
|
45
53
|
|
46
54
|
def get_body_from_node
|
@@ -6,7 +6,6 @@ module Pelusa
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def check(klass)
|
9
|
-
initialize
|
10
9
|
iterate_lines!(klass)
|
11
10
|
|
12
11
|
return SuccessfulAnalysis.new(name) if @ivars.length < limit
|
@@ -27,12 +26,11 @@ module Pelusa
|
|
27
26
|
end
|
28
27
|
|
29
28
|
def iterate_lines!(klass)
|
30
|
-
|
29
|
+
ClassAnalyzer.walk(klass) do |node|
|
31
30
|
if node.is_a?(Rubinius::AST::InstanceVariableAccess) || node.is_a?(Rubinius::AST::InstanceVariableAssignment)
|
32
31
|
@ivars << node.name
|
33
32
|
end
|
34
33
|
end
|
35
|
-
Array(klass).each(&iterator)
|
36
34
|
end
|
37
35
|
end
|
38
36
|
end
|
@@ -6,7 +6,6 @@ module Pelusa
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def check(klass)
|
9
|
-
initialize
|
10
9
|
iterate_lines!(klass)
|
11
10
|
|
12
11
|
return SuccessfulAnalysis.new(name) if lines < limit
|
@@ -31,10 +30,9 @@ module Pelusa
|
|
31
30
|
end
|
32
31
|
|
33
32
|
def iterate_lines!(klass)
|
34
|
-
|
33
|
+
ClassAnalyzer.walk(klass) do |node|
|
35
34
|
@lines << node.line if node.respond_to?(:line)
|
36
35
|
end
|
37
|
-
Array(klass).each(&iterator)
|
38
36
|
end
|
39
37
|
end
|
40
38
|
end
|
@@ -6,7 +6,6 @@ module Pelusa
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def check(klass)
|
9
|
-
initialize
|
10
9
|
iterate_lines!(klass)
|
11
10
|
|
12
11
|
return SuccessfulAnalysis.new(name) if @violations.empty?
|
@@ -27,16 +26,14 @@ module Pelusa
|
|
27
26
|
end
|
28
27
|
|
29
28
|
def iterate_lines!(klass)
|
30
|
-
|
29
|
+
ClassAnalyzer.walk(klass) do |node|
|
31
30
|
if node.respond_to?(:name)
|
32
31
|
name = node.name.respond_to?(:name) ? node.name.name.to_s : node.name.to_s
|
33
32
|
if name =~ /[a-z]/ && name.length > limit
|
34
|
-
|
35
|
-
@violations << [name, node.line]
|
33
|
+
@violations << [name, node.line] unless name =~ /^[A-Z]/ # Ignore constants
|
36
34
|
end
|
37
35
|
end
|
38
36
|
end
|
39
|
-
Array(klass).each(&iterator)
|
40
37
|
end
|
41
38
|
|
42
39
|
def formatted_violations
|
@@ -6,8 +6,8 @@ module Pelusa
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def check(klass)
|
9
|
-
initialize
|
10
9
|
iterate_lines!(klass)
|
10
|
+
|
11
11
|
return SuccessfulAnalysis.new(name) if @violations.empty?
|
12
12
|
|
13
13
|
FailedAnalysis.new(name, formatted_violations) do |violations|
|
@@ -26,12 +26,11 @@ module Pelusa
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def iterate_lines!(klass)
|
29
|
-
|
29
|
+
ClassAnalyzer.walk(klass) do |node|
|
30
30
|
if node.is_a?(Rubinius::AST::Define) && node.arguments.total_args > limit
|
31
31
|
@violations << node.name
|
32
32
|
end
|
33
33
|
end
|
34
|
-
Array(klass).each(&iterator)
|
35
34
|
end
|
36
35
|
|
37
36
|
def formatted_violations
|
@@ -6,7 +6,6 @@ module Pelusa
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def check(klass)
|
9
|
-
initialize
|
10
9
|
iterate_lines!(klass)
|
11
10
|
|
12
11
|
return SuccessfulAnalysis.new(name) if @violations.empty?
|
@@ -23,14 +22,13 @@ module Pelusa
|
|
23
22
|
end
|
24
23
|
|
25
24
|
def iterate_lines!(klass)
|
26
|
-
|
25
|
+
ClassAnalyzer.walk(klass) do |node|
|
27
26
|
if node.is_a?(Rubinius::AST::Send)
|
28
27
|
if [:attr_accessor, :attr_writer, :attr_reader].include? node.name
|
29
28
|
@violations << node.line
|
30
29
|
end
|
31
30
|
end
|
32
31
|
end
|
33
|
-
Array(klass).each(&iterator)
|
34
32
|
end
|
35
33
|
end
|
36
34
|
end
|
@@ -8,7 +8,6 @@ module Pelusa
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def check(klass)
|
11
|
-
initialize
|
12
11
|
iterate_lines!(klass)
|
13
12
|
|
14
13
|
return SuccessfulAnalysis.new(name) if @violations.empty?
|
@@ -25,16 +24,14 @@ module Pelusa
|
|
25
24
|
end
|
26
25
|
|
27
26
|
def iterate_lines!(klass)
|
28
|
-
|
27
|
+
ClassAnalyzer.walk(klass) do |node|
|
29
28
|
if node.respond_to?(:name)
|
30
29
|
name = node.name.respond_to?(:name) ? node.name.name.to_s : node.name.to_s
|
31
30
|
if name =~ /[a-z]/ && name.length < 3 && !RESERVED_NAMES.include?(name)
|
32
|
-
|
33
|
-
@violations << [name, node.line]
|
31
|
+
@violations << [name, node.line] unless name =~ /^[A-Z]/ # Ignore constants
|
34
32
|
end
|
35
33
|
end
|
36
34
|
end
|
37
|
-
Array(klass).each(&iterator)
|
38
35
|
end
|
39
36
|
|
40
37
|
def formatted_violations
|
data/lib/pelusa/report.rb
CHANGED
@@ -6,19 +6,15 @@ module Pelusa
|
|
6
6
|
# Public: Initializes a new Report.
|
7
7
|
#
|
8
8
|
# class_name - The Symbol name of the class being analyzed.
|
9
|
+
# type - the String type of the class being analyzed (class or module).
|
9
10
|
# analyses - An Array of Analysis objects.
|
10
|
-
def initialize(
|
11
|
-
@class_name =
|
11
|
+
def initialize(name, type, analyses)
|
12
|
+
@class_name = name
|
13
|
+
@type = type
|
12
14
|
@analyses = analyses
|
13
15
|
end
|
14
16
|
|
15
|
-
|
16
|
-
@class_name
|
17
|
-
end
|
18
|
-
|
19
|
-
def analyses
|
20
|
-
@analyses
|
21
|
-
end
|
17
|
+
attr_reader :class_name, :type, :analyses
|
22
18
|
|
23
19
|
def successful?
|
24
20
|
@analyses.all? { |analysis| analysis.successful? }
|
data/lib/pelusa/version.rb
CHANGED
@@ -3,30 +3,60 @@ require 'test_helper'
|
|
3
3
|
module Pelusa
|
4
4
|
describe Analyzer do
|
5
5
|
describe '#analyze' do
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
describe 'with a multi-expression AST' do
|
7
|
+
before do
|
8
|
+
@ast = """
|
9
|
+
class Foo
|
10
|
+
def bar
|
11
|
+
123
|
12
|
+
end
|
11
13
|
end
|
12
|
-
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
class Bar
|
16
|
+
def baz
|
17
|
+
321
|
18
|
+
end
|
17
19
|
end
|
18
|
-
end
|
19
|
-
""".to_ast
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
module Baz
|
22
|
+
def bar
|
23
|
+
2.7
|
24
|
+
end
|
25
|
+
end
|
26
|
+
""".to_ast
|
27
|
+
|
28
|
+
lints = stub
|
29
|
+
@analyzer = Analyzer.new([Lint::LineRestriction], RubyReporter, "foo.rb")
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'analyzes an ast and returns a report' do
|
33
|
+
result = @analyzer.analyze(@ast).report
|
34
|
+
result[:filename].must_equal "foo.rb"
|
35
|
+
result[:Foo]["Is below 50 lines"][:status].must_equal "successful"
|
36
|
+
result[:Bar]["Is below 50 lines"][:status].must_equal "successful"
|
37
|
+
result[:Baz]["Is below 50 lines"][:status].must_equal "successful"
|
38
|
+
end
|
23
39
|
end
|
24
40
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
41
|
+
describe 'with a single-expression AST' do
|
42
|
+
before do
|
43
|
+
@ast = """
|
44
|
+
class Foo
|
45
|
+
def bar
|
46
|
+
123
|
47
|
+
end
|
48
|
+
end
|
49
|
+
""".to_ast
|
50
|
+
|
51
|
+
lints = stub
|
52
|
+
@analyzer = Analyzer.new([Lint::LineRestriction], RubyReporter, "foo.rb")
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'analyzes an ast and returns a report' do
|
56
|
+
result = @analyzer.analyze(@ast).report
|
57
|
+
result[:filename].must_equal "foo.rb"
|
58
|
+
result[:Foo]["Is below 50 lines"][:status].must_equal "successful"
|
59
|
+
end
|
30
60
|
end
|
31
61
|
end
|
32
62
|
end
|
@@ -21,6 +21,20 @@ module Pelusa
|
|
21
21
|
@analyzer.class_name.must_equal "Foo"
|
22
22
|
end
|
23
23
|
end
|
24
|
+
|
25
|
+
describe "#type" do
|
26
|
+
it "returns the type module for modules" do
|
27
|
+
# hacky!
|
28
|
+
@klass.stubs(:is_a?).with(Rubinius::AST::Class).returns(true)
|
29
|
+
@analyzer.type.must_equal "class"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "returns the type module for modules" do
|
33
|
+
# hacky!
|
34
|
+
@klass.stubs(:is_a?).with(Rubinius::AST::Class).returns(false)
|
35
|
+
@analyzer.type.must_equal "module"
|
36
|
+
end
|
37
|
+
end
|
24
38
|
end
|
25
39
|
end
|
26
40
|
end
|
@@ -22,6 +22,20 @@ module Pelusa
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
describe 'when the class has no ivars' do
|
26
|
+
it 'returns a SuccessAnalysis' do
|
27
|
+
klass = """
|
28
|
+
class Foo
|
29
|
+
def initialize
|
30
|
+
things = []
|
31
|
+
end
|
32
|
+
end""".to_ast
|
33
|
+
|
34
|
+
analysis = @lint.check(klass)
|
35
|
+
analysis.successful?.must_equal true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
25
39
|
describe 'when the class mixes collection ivars with others' do
|
26
40
|
it 'returns a FailureAnalysis' do
|
27
41
|
klass = """
|
@@ -36,6 +50,37 @@ module Pelusa
|
|
36
50
|
analysis.failed?.must_equal true
|
37
51
|
end
|
38
52
|
end
|
53
|
+
|
54
|
+
describe 'when the class has multiple array assignments' do
|
55
|
+
it 'returns a FailureAnalysis' do
|
56
|
+
klass = """
|
57
|
+
class Foo
|
58
|
+
def initialize
|
59
|
+
@things = []
|
60
|
+
@foos = []
|
61
|
+
end
|
62
|
+
end""".to_ast
|
63
|
+
|
64
|
+
analysis = @lint.check(klass)
|
65
|
+
analysis.failed?.must_equal false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'when the class has multiple array and other assignments' do
|
70
|
+
it 'returns a FailureAnalysis' do
|
71
|
+
klass = """
|
72
|
+
class Foo
|
73
|
+
def initialize
|
74
|
+
@things = []
|
75
|
+
@foos = []
|
76
|
+
@foo = 'bar'
|
77
|
+
end
|
78
|
+
end""".to_ast
|
79
|
+
|
80
|
+
analysis = @lint.check(klass)
|
81
|
+
analysis.failed?.must_equal true
|
82
|
+
end
|
83
|
+
end
|
39
84
|
end
|
40
85
|
end
|
41
86
|
end
|
@@ -47,36 +47,92 @@ module Pelusa
|
|
47
47
|
end""".to_ast
|
48
48
|
|
49
49
|
analysis = @lint.check(klass)
|
50
|
-
analysis.successful?.must_equal true
|
50
|
+
analysis.successful?.must_equal true
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
-
describe 'when chaining operations
|
55
|
-
it 'returns a SuccessAnalysis' do
|
54
|
+
describe 'when chaining whitelisted operations' do
|
55
|
+
it 'returns a SuccessAnalysis for chained operations from Enumerable' do
|
56
|
+
klass = """
|
57
|
+
class Foo
|
58
|
+
def execute
|
59
|
+
[1,2,3].map(&:object_id).each {|i| i}
|
60
|
+
end
|
61
|
+
end""".to_ast
|
62
|
+
|
63
|
+
analysis = @lint.check(klass)
|
64
|
+
analysis.successful?.must_equal true
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns a SuccessAnalysis when chaining methods from Fixnum' do
|
68
|
+
klass = """
|
69
|
+
class Foo
|
70
|
+
def execute
|
71
|
+
1 + 2 + 3 + 4
|
72
|
+
end
|
73
|
+
end""".to_ast
|
74
|
+
|
75
|
+
analysis = @lint.check(klass)
|
76
|
+
analysis.successful?.must_equal true
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'returns a SuccessAnalysis for chained operations from Object' do
|
80
|
+
klass = """
|
81
|
+
class Foo
|
82
|
+
def execute
|
83
|
+
Object.new.to_s.inspect
|
84
|
+
end
|
85
|
+
end""".to_ast
|
86
|
+
|
87
|
+
analysis = @lint.check(klass)
|
88
|
+
analysis.successful?.must_equal true
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'returns a SuccessAnalysis for chained operations from optional sources' do
|
92
|
+
Pelusa.configuration.stubs(:[]).with("DemeterLaw").returns(
|
93
|
+
{"whitelist" => "Object, Kernel, Hash, Enumerable"}
|
94
|
+
)
|
95
|
+
|
56
96
|
klass = """
|
57
97
|
class Foo
|
58
98
|
def execute
|
59
|
-
|
99
|
+
{'a' => 2}.merge.each_pair {|k, v|}
|
60
100
|
end
|
61
101
|
end""".to_ast
|
62
102
|
|
63
103
|
analysis = @lint.check(klass)
|
64
|
-
analysis.successful?.must_equal true
|
104
|
+
analysis.successful?.must_equal true
|
65
105
|
end
|
66
106
|
end
|
67
|
-
end
|
68
107
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
108
|
+
describe 'conversions' do
|
109
|
+
it 'returns a SuccessAnalysis for conversion operations if allowed' do
|
110
|
+
Pelusa.configuration.stubs(:[]).with("DemeterLaw").returns(
|
111
|
+
{"allow_conversions" => true}
|
112
|
+
)
|
113
|
+
|
114
|
+
klass = """
|
115
|
+
class Foo
|
116
|
+
def execute
|
117
|
+
{'a' => 2}.merge({}).to_hash.as_json
|
118
|
+
end
|
119
|
+
end""".to_ast
|
120
|
+
|
121
|
+
analysis = @lint.check(klass)
|
122
|
+
analysis.successful?.must_equal true
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'returns a FailureAnalysis for conversions if not allowed' do
|
126
|
+
klass = """
|
127
|
+
class Foo
|
128
|
+
def execute
|
129
|
+
{'a' => 2}.merge({}).to_hash
|
130
|
+
end
|
131
|
+
end""".to_ast
|
132
|
+
|
133
|
+
analysis = @lint.check(klass)
|
134
|
+
analysis.successful?.must_equal false
|
135
|
+
end
|
80
136
|
end
|
81
137
|
end
|
82
138
|
end
|
@@ -10,8 +10,8 @@ module Pelusa
|
|
10
10
|
okay = SuccessfulAnalysis.new("Is below 50 lines")
|
11
11
|
|
12
12
|
@reports = [
|
13
|
-
Report.new("Foo", [ too_many_lines ]),
|
14
|
-
Report.new("Bar", [ okay ])
|
13
|
+
Report.new("Foo", "class", [ too_many_lines ]),
|
14
|
+
Report.new("Bar", "module", [ okay ])
|
15
15
|
]
|
16
16
|
|
17
17
|
@reporter = RubyReporter.new('foo.rb')
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,32 +1,31 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pelusa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
version: 0.2.2
|
4
|
+
version: 0.2.3
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Josep M. Bach
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2013-09-19 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
|
-
prerelease: false
|
16
|
-
type: :development
|
17
|
-
name: mocha
|
18
14
|
requirement: !ruby/object:Gem::Requirement
|
19
15
|
requirements:
|
20
16
|
- - ! '>='
|
21
17
|
- !ruby/object:Gem::Version
|
22
|
-
version:
|
23
|
-
|
18
|
+
version: !binary |-
|
19
|
+
MA==
|
20
|
+
name: mocha
|
24
21
|
version_requirements: !ruby/object:Gem::Requirement
|
25
22
|
requirements:
|
26
23
|
- - ! '>='
|
27
24
|
- !ruby/object:Gem::Version
|
28
|
-
version:
|
29
|
-
|
25
|
+
version: !binary |-
|
26
|
+
MA==
|
27
|
+
prerelease: false
|
28
|
+
type: :development
|
30
29
|
description: Static analysis Lint-type tool to improve your OO Ruby code
|
31
30
|
email:
|
32
31
|
- josep.m.bach@gmail.com
|
@@ -49,7 +48,6 @@ files:
|
|
49
48
|
- lib/pelusa/class_analyzer.rb
|
50
49
|
- lib/pelusa/cli.rb
|
51
50
|
- lib/pelusa/configuration.rb
|
52
|
-
- lib/pelusa/iterator.rb
|
53
51
|
- lib/pelusa/lint.rb
|
54
52
|
- lib/pelusa/lint/case_statements.rb
|
55
53
|
- lib/pelusa/lint/collection_wrappers.rb
|
@@ -76,7 +74,6 @@ files:
|
|
76
74
|
- test/pelusa/class_analyzer_test.rb
|
77
75
|
- test/pelusa/cli_test.rb
|
78
76
|
- test/pelusa/configuration_test.rb
|
79
|
-
- test/pelusa/iterator_test.rb
|
80
77
|
- test/pelusa/lint/case_statements_test.rb
|
81
78
|
- test/pelusa/lint/collection_wrappers_test.rb
|
82
79
|
- test/pelusa/lint/demeter_law_test.rb
|
@@ -95,6 +92,7 @@ files:
|
|
95
92
|
- test/test_helper.rb
|
96
93
|
homepage: http://github.com/codegram/pelusa
|
97
94
|
licenses: []
|
95
|
+
metadata: {}
|
98
96
|
post_install_message:
|
99
97
|
rdoc_options: []
|
100
98
|
require_paths:
|
@@ -103,19 +101,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
103
101
|
requirements:
|
104
102
|
- - ! '>='
|
105
103
|
- !ruby/object:Gem::Version
|
106
|
-
version:
|
107
|
-
|
104
|
+
version: !binary |-
|
105
|
+
MA==
|
108
106
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
107
|
requirements:
|
110
108
|
- - ! '>='
|
111
109
|
- !ruby/object:Gem::Version
|
112
|
-
version:
|
113
|
-
|
110
|
+
version: !binary |-
|
111
|
+
MA==
|
114
112
|
requirements: []
|
115
113
|
rubyforge_project: pelusa
|
116
|
-
rubygems_version:
|
114
|
+
rubygems_version: 2.0.6
|
117
115
|
signing_key:
|
118
|
-
specification_version:
|
116
|
+
specification_version: 4
|
119
117
|
summary: Static analysis Lint-type tool to improve your OO Ruby code
|
120
118
|
test_files:
|
121
119
|
- test/pelusa_test.rb
|
@@ -125,7 +123,6 @@ test_files:
|
|
125
123
|
- test/pelusa/class_analyzer_test.rb
|
126
124
|
- test/pelusa/cli_test.rb
|
127
125
|
- test/pelusa/configuration_test.rb
|
128
|
-
- test/pelusa/iterator_test.rb
|
129
126
|
- test/pelusa/runner_test.rb
|
130
127
|
- test/pelusa/lint/case_statements_test.rb
|
131
128
|
- test/pelusa/lint/collection_wrappers_test.rb
|
data/lib/pelusa/iterator.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
module Pelusa
|
2
|
-
class Iterator
|
3
|
-
NodeIterator = lambda do |node, check|
|
4
|
-
check.call(node)
|
5
|
-
|
6
|
-
if node.respond_to?(:each)
|
7
|
-
return node.each { |node| NodeIterator.call(node, check) }
|
8
|
-
end
|
9
|
-
|
10
|
-
ivars = node.instance_variables
|
11
|
-
children = ivars.map { |ivar| node.instance_variable_get(ivar) }
|
12
|
-
|
13
|
-
return children.each { |node| NodeIterator.call(node, check) }
|
14
|
-
end
|
15
|
-
|
16
|
-
# Public: Initializes a new Iterator with a particular lint check.
|
17
|
-
#
|
18
|
-
# lint - The lint block that yields a node to assert for particular
|
19
|
-
# conditions in that node.
|
20
|
-
def initialize(&lint)
|
21
|
-
@iterator = lambda { |node| NodeIterator.call(node, lint) }
|
22
|
-
end
|
23
|
-
|
24
|
-
# Public: Calls the iterator with the given arguments.
|
25
|
-
#
|
26
|
-
# node - The root node from which to iterate.
|
27
|
-
def call(node)
|
28
|
-
@iterator.call(node)
|
29
|
-
end
|
30
|
-
|
31
|
-
# Public: Returns the iterator. Useful when using symbol to proc
|
32
|
-
# conversions, such as &iterator.
|
33
|
-
#
|
34
|
-
# Returns the Proc iterator.
|
35
|
-
def to_proc
|
36
|
-
@iterator
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
module Pelusa
|
4
|
-
describe Iterator do
|
5
|
-
before do
|
6
|
-
@last_node = nil
|
7
|
-
@iterator = Iterator.new do |node|
|
8
|
-
@last_node = node if node == 3
|
9
|
-
end
|
10
|
-
|
11
|
-
@node = [ [ 1, [ 2, 3 ] ] ]
|
12
|
-
end
|
13
|
-
|
14
|
-
describe '#call' do
|
15
|
-
it 'calls the iterator on a node' do
|
16
|
-
@iterator.call(@node)
|
17
|
-
@last_node.must_equal 3
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
describe '#to_proc' do
|
22
|
-
it 'calls the iterator on a node' do
|
23
|
-
@iterator.to_proc[@node]
|
24
|
-
@last_node.must_equal 3
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|