pelusa 0.0.1
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.
- 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
|