pelusa 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +5 -0
  2. data/.rvmrc +1 -0
  3. data/.travis.yml +4 -0
  4. data/Gemfile +4 -0
  5. data/Rakefile +10 -0
  6. data/Readme.md +74 -0
  7. data/bin/pelusa +7 -0
  8. data/lib/pelusa.rb +22 -0
  9. data/lib/pelusa/analysis.rb +88 -0
  10. data/lib/pelusa/analyzer.rb +48 -0
  11. data/lib/pelusa/class_analyzer.rb +28 -0
  12. data/lib/pelusa/cli.rb +27 -0
  13. data/lib/pelusa/iterator.rb +39 -0
  14. data/lib/pelusa/lint.rb +29 -0
  15. data/lib/pelusa/lint/collection_wrappers.rb +50 -0
  16. data/lib/pelusa/lint/demeter_law.rb +35 -0
  17. data/lib/pelusa/lint/else_clauses.rb +40 -0
  18. data/lib/pelusa/lint/indentation_level.rb +94 -0
  19. data/lib/pelusa/lint/instance_variables.rb +39 -0
  20. data/lib/pelusa/lint/line_restriction.rb +41 -0
  21. data/lib/pelusa/lint/properties.rb +37 -0
  22. data/lib/pelusa/lint/short_identifiers.rb +54 -0
  23. data/lib/pelusa/report.rb +27 -0
  24. data/lib/pelusa/reporters/reporter.rb +22 -0
  25. data/lib/pelusa/reporters/ruby_reporter.rb +29 -0
  26. data/lib/pelusa/reporters/stdout_reporter.rb +50 -0
  27. data/lib/pelusa/runner.rb +53 -0
  28. data/lib/pelusa/version.rb +3 -0
  29. data/pelusa.gemspec +22 -0
  30. data/test/pelusa/analysis_test.rb +38 -0
  31. data/test/pelusa/analyzer_test.rb +33 -0
  32. data/test/pelusa/class_analyzer_test.rb +26 -0
  33. data/test/pelusa/iterator_test.rb +28 -0
  34. data/test/pelusa/lint/collection_wrappers_test.rb +42 -0
  35. data/test/pelusa/lint/demeter_law_test.rb +42 -0
  36. data/test/pelusa/lint/else_clauses_test.rb +50 -0
  37. data/test/pelusa/lint/indentation_level_test.rb +47 -0
  38. data/test/pelusa/lint/instance_variables_test.rb +44 -0
  39. data/test/pelusa/lint/line_restriction_test.rb +40 -0
  40. data/test/pelusa/lint/properties_test.rb +44 -0
  41. data/test/pelusa/lint/short_identifiers_test.rb +41 -0
  42. data/test/pelusa/reporters/ruby_reporter_test.rb +42 -0
  43. data/test/pelusa/runner_test.rb +27 -0
  44. data/test/pelusa_test.rb +19 -0
  45. data/test/test_helper.rb +5 -0
  46. 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