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