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