pelusa 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rvmrc +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Rakefile +10 -0
- data/Readme.md +74 -0
- data/bin/pelusa +7 -0
- data/lib/pelusa.rb +22 -0
- data/lib/pelusa/analysis.rb +88 -0
- data/lib/pelusa/analyzer.rb +48 -0
- data/lib/pelusa/class_analyzer.rb +28 -0
- data/lib/pelusa/cli.rb +27 -0
- data/lib/pelusa/iterator.rb +39 -0
- data/lib/pelusa/lint.rb +29 -0
- data/lib/pelusa/lint/collection_wrappers.rb +50 -0
- data/lib/pelusa/lint/demeter_law.rb +35 -0
- data/lib/pelusa/lint/else_clauses.rb +40 -0
- data/lib/pelusa/lint/indentation_level.rb +94 -0
- data/lib/pelusa/lint/instance_variables.rb +39 -0
- data/lib/pelusa/lint/line_restriction.rb +41 -0
- data/lib/pelusa/lint/properties.rb +37 -0
- data/lib/pelusa/lint/short_identifiers.rb +54 -0
- data/lib/pelusa/report.rb +27 -0
- data/lib/pelusa/reporters/reporter.rb +22 -0
- data/lib/pelusa/reporters/ruby_reporter.rb +29 -0
- data/lib/pelusa/reporters/stdout_reporter.rb +50 -0
- data/lib/pelusa/runner.rb +53 -0
- data/lib/pelusa/version.rb +3 -0
- data/pelusa.gemspec +22 -0
- data/test/pelusa/analysis_test.rb +38 -0
- data/test/pelusa/analyzer_test.rb +33 -0
- data/test/pelusa/class_analyzer_test.rb +26 -0
- data/test/pelusa/iterator_test.rb +28 -0
- data/test/pelusa/lint/collection_wrappers_test.rb +42 -0
- data/test/pelusa/lint/demeter_law_test.rb +42 -0
- data/test/pelusa/lint/else_clauses_test.rb +50 -0
- data/test/pelusa/lint/indentation_level_test.rb +47 -0
- data/test/pelusa/lint/instance_variables_test.rb +44 -0
- data/test/pelusa/lint/line_restriction_test.rb +40 -0
- data/test/pelusa/lint/properties_test.rb +44 -0
- data/test/pelusa/lint/short_identifiers_test.rb +41 -0
- data/test/pelusa/reporters/ruby_reporter_test.rb +42 -0
- data/test/pelusa/runner_test.rb +27 -0
- data/test/pelusa_test.rb +19 -0
- data/test/test_helper.rb +5 -0
- metadata +118 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
module Pelusa
|
2
|
+
module Lint
|
3
|
+
class DemeterLaw
|
4
|
+
def initialize
|
5
|
+
@violations = Set.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def check(klass)
|
9
|
+
initialize
|
10
|
+
iterate_lines!(klass)
|
11
|
+
|
12
|
+
return SuccessfulAnalysis.new(name) if @violations.empty?
|
13
|
+
|
14
|
+
FailedAnalysis.new(name, @violations) do |violations|
|
15
|
+
"There are #{violations.length} Demeter law violations in lines #{violations.to_a.join(', ')}."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def name
|
22
|
+
"Respects Demeter law"
|
23
|
+
end
|
24
|
+
|
25
|
+
def iterate_lines!(klass)
|
26
|
+
iterator = Iterator.new do |node|
|
27
|
+
if node.is_a?(Rubinius::AST::Send) && node.receiver.is_a?(Rubinius::AST::Send)
|
28
|
+
@violations << node.line
|
29
|
+
end
|
30
|
+
end
|
31
|
+
Array(klass).each(&iterator)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Pelusa
|
2
|
+
module Lint
|
3
|
+
class ElseClauses
|
4
|
+
def initialize
|
5
|
+
@violations = Set.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def check(klass)
|
9
|
+
initialize
|
10
|
+
iterate_lines!(klass)
|
11
|
+
|
12
|
+
return SuccessfulAnalysis.new(name) if @violations.empty?
|
13
|
+
|
14
|
+
FailedAnalysis.new(name, @violations) do |violations|
|
15
|
+
"There are #{violations.length} else clauses in lines #{violations.to_a.join(', ')}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def name
|
22
|
+
"Doesn't use else clauses"
|
23
|
+
end
|
24
|
+
|
25
|
+
def iterate_lines!(klass)
|
26
|
+
iterator = Iterator.new do |node|
|
27
|
+
if node.is_a?(Rubinius::AST::If)
|
28
|
+
has_body = node.body && !node.body.is_a?(Rubinius::AST::NilLiteral)
|
29
|
+
has_else = node.else && !node.else.is_a?(Rubinius::AST::NilLiteral)
|
30
|
+
|
31
|
+
if has_body && has_else
|
32
|
+
@violations << node.else.line
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
Array(klass).each(&iterator)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Pelusa
|
2
|
+
module Lint
|
3
|
+
class IndentationLevel
|
4
|
+
def initialize
|
5
|
+
@violations = Set.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def check(klass)
|
9
|
+
initialize
|
10
|
+
iterate_lines!(klass)
|
11
|
+
|
12
|
+
return SuccessfulAnalysis.new(name) if @violations.empty?
|
13
|
+
|
14
|
+
FailedAnalysis.new(name, @violations) do |violations|
|
15
|
+
"There's too much indentation in lines #{violations.to_a.join(', ')}."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def name
|
22
|
+
"Doesn't use more than one indentation level inside methods"
|
23
|
+
end
|
24
|
+
|
25
|
+
def iterate_lines!(klass)
|
26
|
+
iterator = Iterator.new do |node|
|
27
|
+
if node.is_a?(Rubinius::AST::Define)
|
28
|
+
_iterate = Iterator.new do |node|
|
29
|
+
__iterate = Iterator.new do |node|
|
30
|
+
if body = get_body_from_node[node]
|
31
|
+
if node.line != [body].flatten.first.line
|
32
|
+
@violations << body.line
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Array(get_body_from_node[node]).each(&__iterate)
|
38
|
+
end
|
39
|
+
node.body.array.each(&_iterate)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
Array(klass).each(&iterator)
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_body_from_node
|
47
|
+
lambda do |node|
|
48
|
+
if node.respond_to?(:body) && !node.body.is_a?(Rubinius::AST::NilLiteral)
|
49
|
+
node.body
|
50
|
+
elsif node.respond_to?(:else)
|
51
|
+
node.else
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
def check_indentation_levels!
|
59
|
+
violations = Set.new
|
60
|
+
|
61
|
+
get_body_from_node = lambda do |node|
|
62
|
+
if node.respond_to?(:body) && !node.body.is_a?(Rubinius::AST::NilLiteral)
|
63
|
+
node.body
|
64
|
+
elsif node.respond_to?(:else)
|
65
|
+
node.else
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
iterate = self.class.iterator do |node|
|
70
|
+
if node.is_a?(Rubinius::AST::Define)
|
71
|
+
_iterate = self.class.iterator do |node|
|
72
|
+
__iterate = self.class.iterator do |node|
|
73
|
+
if body = get_body_from_node[node]
|
74
|
+
if node.line != [body].flatten.first.line
|
75
|
+
violations << body.line
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
Array(get_body_from_node[node]).each(&__iterate)
|
81
|
+
end
|
82
|
+
node.body.array.each(&_iterate)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
report "Doesn't use more than one indentation level" do
|
87
|
+
Array(ast).each(&iterate)
|
88
|
+
if violations.empty?
|
89
|
+
Report.new
|
90
|
+
else
|
91
|
+
Report.new(violations) { |violations| "There's too much of indentation at lines #{violations.to_a.join(', ')}." }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Pelusa
|
2
|
+
module Lint
|
3
|
+
class InstanceVariables
|
4
|
+
def initialize
|
5
|
+
@ivars = Set.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def check(klass)
|
9
|
+
initialize
|
10
|
+
iterate_lines!(klass)
|
11
|
+
|
12
|
+
return SuccessfulAnalysis.new(name) if @ivars.length < limit
|
13
|
+
|
14
|
+
FailedAnalysis.new(name, @ivars) do |ivars|
|
15
|
+
"This class uses #{ivars.length} instance variables: #{ivars.to_a.join(', ')}."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def name
|
22
|
+
"Uses less than #{limit} ivars"
|
23
|
+
end
|
24
|
+
|
25
|
+
def limit
|
26
|
+
3
|
27
|
+
end
|
28
|
+
|
29
|
+
def iterate_lines!(klass)
|
30
|
+
iterator = Iterator.new do |node|
|
31
|
+
if node.is_a?(Rubinius::AST::InstanceVariableAccess) || node.is_a?(Rubinius::AST::InstanceVariableAssignment)
|
32
|
+
@ivars << node.name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
Array(klass).each(&iterator)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Pelusa
|
2
|
+
module Lint
|
3
|
+
class LineRestriction
|
4
|
+
def initialize
|
5
|
+
@lines = Set.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def check(klass)
|
9
|
+
initialize
|
10
|
+
iterate_lines!(klass)
|
11
|
+
|
12
|
+
return SuccessfulAnalysis.new(name) if lines < limit
|
13
|
+
|
14
|
+
FailedAnalysis.new(name, lines) do |lines|
|
15
|
+
"This class has #{lines} lines."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def name
|
22
|
+
"Is below #{limit} lines"
|
23
|
+
end
|
24
|
+
|
25
|
+
def limit
|
26
|
+
50
|
27
|
+
end
|
28
|
+
|
29
|
+
def lines
|
30
|
+
@lines.max - @lines.min
|
31
|
+
end
|
32
|
+
|
33
|
+
def iterate_lines!(klass)
|
34
|
+
iterator = Iterator.new do |node|
|
35
|
+
@lines << node.line if node.respond_to?(:line)
|
36
|
+
end
|
37
|
+
Array(klass).each(&iterator)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Pelusa
|
2
|
+
module Lint
|
3
|
+
class Properties
|
4
|
+
def initialize
|
5
|
+
@violations = Set.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def check(klass)
|
9
|
+
initialize
|
10
|
+
iterate_lines!(klass)
|
11
|
+
|
12
|
+
return SuccessfulAnalysis.new(name) if @violations.empty?
|
13
|
+
|
14
|
+
FailedAnalysis.new(name, @violations) do |violations|
|
15
|
+
"There are getters, setters or properties in lines #{violations.to_a.join(', ')}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def name
|
22
|
+
"Doesn't use getters, setters or properties"
|
23
|
+
end
|
24
|
+
|
25
|
+
def iterate_lines!(klass)
|
26
|
+
iterator = Iterator.new do |node|
|
27
|
+
if node.is_a?(Rubinius::AST::Send)
|
28
|
+
if [:attr_accessor, :attr_writer, :attr_reader].include? node.name
|
29
|
+
@violations << node.line
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
Array(klass).each(&iterator)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Pelusa
|
2
|
+
module Lint
|
3
|
+
class ShortIdentifiers
|
4
|
+
def initialize
|
5
|
+
@violations = Set.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def check(klass)
|
9
|
+
initialize
|
10
|
+
iterate_lines!(klass)
|
11
|
+
|
12
|
+
return SuccessfulAnalysis.new(name) if @violations.empty?
|
13
|
+
|
14
|
+
FailedAnalysis.new(name, formatted_violations) do |violations|
|
15
|
+
"Names are too short: #{violations.join(', ')}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def name
|
22
|
+
"Uses descriptive names"
|
23
|
+
end
|
24
|
+
|
25
|
+
def iterate_lines!(klass)
|
26
|
+
iterator = Iterator.new do |node|
|
27
|
+
if node.respond_to?(:name)
|
28
|
+
name = node.name.respond_to?(:name) ? node.name.name.to_s : node.name.to_s
|
29
|
+
if name =~ /[a-z]/ && name.length < 3 && !["p", "pp"].include?(name)
|
30
|
+
next if name =~ /^[A-Z]/ # Ignore constants
|
31
|
+
@violations << [name, node.line]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
Array(klass).each(&iterator)
|
36
|
+
end
|
37
|
+
|
38
|
+
def formatted_violations
|
39
|
+
grouped_violations = @violations.inject({}) do |hash, (name, line)|
|
40
|
+
hash[name] ||= []
|
41
|
+
hash[name] << line
|
42
|
+
hash
|
43
|
+
end
|
44
|
+
|
45
|
+
violations = []
|
46
|
+
|
47
|
+
grouped_violations.each_pair do |name, lines|
|
48
|
+
violations << "#{name} (line #{lines.join(', ')})"
|
49
|
+
end
|
50
|
+
violations
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Pelusa
|
2
|
+
# Public: A Report is a wrapper that relates a class name with all its
|
3
|
+
# analyses for different lint checks.
|
4
|
+
#
|
5
|
+
class Report
|
6
|
+
# Public: Initializes a new Report.
|
7
|
+
#
|
8
|
+
# class_name - The Symbol name of the class being analyzed.
|
9
|
+
# analyses - An Array of Analysis objects.
|
10
|
+
def initialize(class_name, analyses)
|
11
|
+
@class_name = class_name
|
12
|
+
@analyses = analyses
|
13
|
+
end
|
14
|
+
|
15
|
+
def class_name
|
16
|
+
@class_name
|
17
|
+
end
|
18
|
+
|
19
|
+
def analyses
|
20
|
+
@analyses
|
21
|
+
end
|
22
|
+
|
23
|
+
def successful?
|
24
|
+
@analyses.all? { |analysis| analysis.successful? }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Pelusa
|
2
|
+
class Reporter
|
3
|
+
def self.print_banner
|
4
|
+
end
|
5
|
+
|
6
|
+
def initialize(filename)
|
7
|
+
@filename = filename
|
8
|
+
end
|
9
|
+
|
10
|
+
def reports=(reports)
|
11
|
+
@reports = reports
|
12
|
+
end
|
13
|
+
|
14
|
+
def report
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def successful?
|
19
|
+
@reports.all? { |report| report.successful? }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Pelusa
|
4
|
+
class RubyReporter < Reporter
|
5
|
+
def report
|
6
|
+
hash = @reports.inject({}) do |acc, report|
|
7
|
+
class_name = report.class_name
|
8
|
+
acc[class_name] = hashify_report(report)
|
9
|
+
acc
|
10
|
+
end
|
11
|
+
hash[:filename] = @filename unless hash.empty?
|
12
|
+
hash
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def hashify_report(report)
|
18
|
+
analyses = report.analyses
|
19
|
+
analyses.inject({}) do |acc, analysis|
|
20
|
+
name = analysis.name
|
21
|
+
acc[name] = {
|
22
|
+
status: analysis.status,
|
23
|
+
message: analysis.message,
|
24
|
+
}
|
25
|
+
acc
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Pelusa
|
4
|
+
class StdoutReporter < Reporter
|
5
|
+
def self.print_banner
|
6
|
+
puts " \e[0;35mϟ\e[0m \e[0;32mPelusa \e[0;35mϟ\e[0m"
|
7
|
+
puts " \e[0;37m----------\e[0m"
|
8
|
+
end
|
9
|
+
|
10
|
+
def report
|
11
|
+
puts " \e[0;36m#{@filename}\e[0m"
|
12
|
+
puts
|
13
|
+
|
14
|
+
@reports.each do |class_report|
|
15
|
+
print_report(class_report)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def print_report(class_report)
|
22
|
+
class_name = class_report.class_name
|
23
|
+
|
24
|
+
puts " class #{class_name}"
|
25
|
+
|
26
|
+
analyses = class_report.analyses
|
27
|
+
analyses.each do |analysis|
|
28
|
+
print_analysis(analysis)
|
29
|
+
end
|
30
|
+
puts
|
31
|
+
end
|
32
|
+
|
33
|
+
def print_analysis(analysis)
|
34
|
+
name = analysis.name
|
35
|
+
status = analysis.status
|
36
|
+
message = analysis.message
|
37
|
+
|
38
|
+
print " \e[0;33m✿ %s \e[0m" % name
|
39
|
+
|
40
|
+
if analysis.successful?
|
41
|
+
print "\e[0;32m✓\e[0m\n"
|
42
|
+
return
|
43
|
+
end
|
44
|
+
|
45
|
+
print "\e[0;31m✗\n\t"
|
46
|
+
puts message
|
47
|
+
print "\e[0m"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|