roodi1.9 2.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 (64) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +0 -0
  3. data/History.txt +88 -0
  4. data/Manifest.txt +56 -0
  5. data/README.md +123 -0
  6. data/Rakefile +40 -0
  7. data/bin/roodi1.9 +21 -0
  8. data/bin/roodi1.9-describe +7 -0
  9. data/lib/roodi/checks/abc_metric_method_check.rb +77 -0
  10. data/lib/roodi/checks/assignment_in_conditional_check.rb +34 -0
  11. data/lib/roodi/checks/case_missing_else_check.rb +20 -0
  12. data/lib/roodi/checks/check.rb +61 -0
  13. data/lib/roodi/checks/class_line_count_check.rb +18 -0
  14. data/lib/roodi/checks/class_name_check.rb +21 -0
  15. data/lib/roodi/checks/class_variable_check.rb +24 -0
  16. data/lib/roodi/checks/control_coupling_check.rb +20 -0
  17. data/lib/roodi/checks/cyclomatic_complexity_block_check.rb +39 -0
  18. data/lib/roodi/checks/cyclomatic_complexity_check.rb +47 -0
  19. data/lib/roodi/checks/cyclomatic_complexity_method_check.rb +40 -0
  20. data/lib/roodi/checks/empty_rescue_body_check.rb +32 -0
  21. data/lib/roodi/checks/for_loop_check.rb +20 -0
  22. data/lib/roodi/checks/line_count_check.rb +29 -0
  23. data/lib/roodi/checks/method_line_count_check.rb +19 -0
  24. data/lib/roodi/checks/method_name_check.rb +21 -0
  25. data/lib/roodi/checks/missing_foreign_key_index_check.rb +98 -0
  26. data/lib/roodi/checks/module_line_count_check.rb +18 -0
  27. data/lib/roodi/checks/module_name_check.rb +21 -0
  28. data/lib/roodi/checks/name_check.rb +23 -0
  29. data/lib/roodi/checks/npath_complexity_check.rb +73 -0
  30. data/lib/roodi/checks/npath_complexity_method_check.rb +28 -0
  31. data/lib/roodi/checks/parameter_number_check.rb +30 -0
  32. data/lib/roodi/checks.rb +18 -0
  33. data/lib/roodi/core/checking_visitor.rb +26 -0
  34. data/lib/roodi/core/error.rb +17 -0
  35. data/lib/roodi/core/parser.rb +30 -0
  36. data/lib/roodi/core/runner.rb +80 -0
  37. data/lib/roodi/core/visitable_sexp.rb +25 -0
  38. data/lib/roodi/core.rb +1 -0
  39. data/lib/roodi.rb +6 -0
  40. data/lib/roodi_task.rb +35 -0
  41. data/roodi.yml +19 -0
  42. data/roodi1.9.gemspec +17 -0
  43. data/spec/roodi/checks/abc_metric_method_check_spec.rb +89 -0
  44. data/spec/roodi/checks/assignment_in_conditional_check_spec.rb +105 -0
  45. data/spec/roodi/checks/case_missing_else_check_spec.rb +32 -0
  46. data/spec/roodi/checks/class_line_count_check_spec.rb +39 -0
  47. data/spec/roodi/checks/class_name_check_spec.rb +39 -0
  48. data/spec/roodi/checks/class_variable_check_spec.rb +17 -0
  49. data/spec/roodi/checks/control_coupling_check_spec.rb +23 -0
  50. data/spec/roodi/checks/cyclomatic_complexity_block_check_spec.rb +67 -0
  51. data/spec/roodi/checks/cyclomatic_complexity_method_check_spec.rb +200 -0
  52. data/spec/roodi/checks/empty_rescue_body_check_spec.rb +140 -0
  53. data/spec/roodi/checks/for_loop_check_spec.rb +18 -0
  54. data/spec/roodi/checks/method_line_count_check_spec.rb +56 -0
  55. data/spec/roodi/checks/method_name_check_spec.rb +76 -0
  56. data/spec/roodi/checks/missing_foreign_key_index_check_spec.rb +33 -0
  57. data/spec/roodi/checks/module_line_count_check_spec.rb +39 -0
  58. data/spec/roodi/checks/module_name_check_spec.rb +27 -0
  59. data/spec/roodi/checks/npath_complexity_method_check_spec.rb +53 -0
  60. data/spec/roodi/checks/parameter_number_check_spec.rb +47 -0
  61. data/spec/roodi/core/runner_spec.rb +25 -0
  62. data/spec/roodi/roodi.yml +1 -0
  63. data/spec/spec_helper.rb +3 -0
  64. metadata +112 -0
@@ -0,0 +1,47 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class CyclomaticComplexityCheck < Check
6
+ COMPLEXITY_NODE_TYPES = [:if, :while, :until, :for, :rescue, :case, :when, :and, :or]
7
+
8
+ def initialize(complexity)
9
+ super()
10
+ @complexity = complexity
11
+ @count = 0
12
+ @counting = 0
13
+ end
14
+
15
+ COMPLEXITY_NODE_TYPES.each do |type|
16
+ define_method "evaluate_start_#{type}" do |node|
17
+ @count = @count + 1 if counting?
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ def count_complexity(node)
24
+ count_branches(node) + 1
25
+ end
26
+
27
+ def increase_depth
28
+ @count = 1 unless counting?
29
+ @counting = @counting + 1
30
+ end
31
+
32
+ def decrease_depth
33
+ @counting = @counting - 1
34
+ if @counting <= 0
35
+ @counting = 0
36
+ evaluate_matching_end
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def counting?
43
+ @counting > 0
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,40 @@
1
+ require 'roodi/checks/cyclomatic_complexity_check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks cyclomatic complexity of a method against a specified limit.
6
+ #
7
+ # The cyclomatic complexity is measured by the number of "if", "unless", "elsif", "?:",
8
+ # "while", "until", "for", "rescue", "case", "when", "&amp;&amp;", "and", "||" and "or"
9
+ # statements (plus one) in the body of the member. It is a measure of the minimum
10
+ # number of possible paths through the source and therefore the number of required tests.
11
+ #
12
+ # Generally, for a method, 1-4 is considered good, 5-8 ok, 9-10 consider re-factoring, and
13
+ # 11+ re-factor now!
14
+ class CyclomaticComplexityMethodCheck < CyclomaticComplexityCheck
15
+ DEFAULT_COMPLEXITY = 8
16
+
17
+ def initialize(options = {})
18
+ complexity = options['complexity'] || DEFAULT_COMPLEXITY
19
+ super(complexity)
20
+ end
21
+
22
+ def interesting_nodes
23
+ [:defn] + COMPLEXITY_NODE_TYPES
24
+ end
25
+
26
+ def evaluate_start_defn(node)
27
+ @method_name = @node[1]
28
+ increase_depth
29
+ end
30
+
31
+ def evaluate_end_defn(node)
32
+ decrease_depth
33
+ end
34
+
35
+ def evaluate_matching_end
36
+ add_error "Method name \"#{@method_name}\" cyclomatic complexity is #{@count}. It should be #{@complexity} or less." unless @count <= @complexity
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,32 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks the body of a rescue block to make sure it's not empty..
6
+ #
7
+ # When the body of a rescue block is empty, exceptions can get caught and swallowed without
8
+ # any feedback to the user.
9
+ class EmptyRescueBodyCheck < Check
10
+ STATEMENT_NODES = [:fcall, :return, :attrasgn, :vcall, :nil, :call, :lasgn, :true, :false]
11
+
12
+ def interesting_nodes
13
+ [:resbody]
14
+ end
15
+
16
+ def evaluate_start(node)
17
+ add_error("Rescue block should not be empty.") unless has_statement?(node.children[1])
18
+ end
19
+
20
+ private
21
+
22
+ def has_statement?(node)
23
+ false unless node
24
+ has_local_statement?(node) or node.children.any? { |child| has_statement?(child) } if node
25
+ end
26
+
27
+ def has_local_statement?(node)
28
+ STATEMENT_NODES.include?(node.node_type)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks to make sure for loops are not being used..
6
+ #
7
+ # Using a for loop is not idiomatic use of Ruby, and is usually a sign that someone with
8
+ # more experience in a different programming language is trying out Ruby. Use
9
+ # Enumerable.each with a block instead.
10
+ class ForLoopCheck < Check
11
+ def interesting_nodes
12
+ [:for]
13
+ end
14
+
15
+ def evaluate_start(node)
16
+ add_error "Don't use 'for' loops. Use Enumerable.each instead."
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class LineCountCheck < Check
6
+ def initialize(interesting_nodes, line_count, message_prefix)
7
+ super()
8
+ @interesting_nodes = interesting_nodes
9
+ @line_count = line_count
10
+ @message_prefix = message_prefix
11
+ end
12
+
13
+ def interesting_nodes
14
+ @interesting_nodes
15
+ end
16
+
17
+ def evaluate_start(node)
18
+ line_count = count_lines(node)
19
+ add_error "#{@message_prefix} \"#{node[1]}\" has #{line_count} lines. It should have #{@line_count} or less." unless line_count <= @line_count
20
+ end
21
+
22
+ protected
23
+
24
+ def count_lines(node)
25
+ node.last.line - node.line - 1
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ require 'roodi/checks/line_count_check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks a method to make sure the number of lines it has is under the specified limit.
6
+ #
7
+ # A method getting too large is a code smell that indicates it might be doing more than one
8
+ # thing and becoming hard to test. It should probably be refactored into multiple methods
9
+ # that each do a single thing well.
10
+ class MethodLineCountCheck < LineCountCheck
11
+ DEFAULT_LINE_COUNT = 20
12
+
13
+ def initialize(options = {})
14
+ line_count = options['line_count'] || DEFAULT_LINE_COUNT
15
+ super([:defn], line_count, 'Method')
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ require 'roodi/checks/name_check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks a method name to make sure it matches the specified pattern.
6
+ #
7
+ # Keeping to a consistent nameing convention makes your code easier to read.
8
+ class MethodNameCheck < NameCheck
9
+ DEFAULT_PATTERN = /^[_a-z<>=\[|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/
10
+
11
+ def initialize(options = {})
12
+ pattern = options['pattern'] || DEFAULT_PATTERN
13
+ super([:defn], pattern, 'Method')
14
+ end
15
+
16
+ def find_name(node)
17
+ node[1]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,98 @@
1
+ require 'roodi/checks/check'
2
+ require 'pathname'
3
+
4
+ module Roodi
5
+ module Checks
6
+ # Checks to make sure for loops are not being used..
7
+ #
8
+ # Using a for loop is not idiomatic use of Ruby, and is usually a sign that someone with
9
+ # more experience in a different programming language is trying out Ruby. Use
10
+ # Enumerable.each with a block instead.
11
+ class MissingForeignKeyIndexCheck < Check
12
+ def initialize
13
+ @foreign_keys = {}
14
+ @indexes = {}
15
+ end
16
+
17
+ def interesting_nodes
18
+ [:call]
19
+ end
20
+
21
+ def evaluate_start_call(node)
22
+ if analyzing_schema(node)
23
+ if creating_table(node)
24
+ @current_table = create_table_name(node)
25
+ end
26
+
27
+ if creating_foreign_key(node)
28
+ @foreign_keys[@current_table] ||= []
29
+ @foreign_keys[@current_table] << foreign_key_column_name(node)
30
+ end
31
+
32
+ if adding_index(node)
33
+ @indexes[index_table_name(node)] ||= []
34
+ @indexes[index_table_name(node)] << index_column_name(node)
35
+ end
36
+ end
37
+ end
38
+
39
+ def evaluate_end_call(node)
40
+ #ignored
41
+ end
42
+
43
+ def analyzing_schema(node)
44
+ pathname = Pathname.new(node.file)
45
+ @analyzing_schema ||= ("schema.rb" == pathname.basename.to_s)
46
+ end
47
+
48
+ def creating_table(node)
49
+ :create_table == node[2]
50
+ end
51
+
52
+ def create_table_name(node)
53
+ # Get table name out of this:
54
+ # s(:call, nil, :create_table, s(:arglist, s(:str, "duplicate_blocks"), s(:hash, s(:lit, :force), s(:true))))
55
+ node[3][1][1]
56
+ end
57
+
58
+ def creating_foreign_key(node)
59
+ #s(:call, s(:lvar, :t), :integer, s(:arglist, s(:str, "duplicate_set_id"), s(:hash, s(:lit, :null), s(:false))))
60
+ column_type = node[2]
61
+ column_name = node[3][1][1]
62
+ :integer == column_type && "_id" == column_name[-3,3]
63
+ end
64
+
65
+ def foreign_key_column_name(node)
66
+ #s(:call, s(:lvar, :t), :integer, s(:arglist, s(:str, "duplicate_set_id"), s(:hash, s(:lit, :null), s(:false))))
67
+ column_name = node[3][1][1]
68
+ end
69
+
70
+ def adding_index(node)
71
+ :add_index == node[2]
72
+ end
73
+
74
+ def index_table_name(node)
75
+ # Get table name out of this:
76
+ # s(:call, nil, :add_index, s(:arglist, s(:str, "duplicate_blocks"), s(:array, s(:str, "duplicate_set_id")), s(:hash, s(:lit, :name), s(:str, "index_duplicate_blocks_on_duplicate_set_id"))))
77
+ node[3][1][1]
78
+ end
79
+
80
+ def index_column_name(node)
81
+ # Get index column name out of this:
82
+ # s(:call, nil, :add_index, s(:arglist, s(:str, "duplicate_blocks"), s(:array, s(:str, "duplicate_set_id")), s(:hash, s(:lit, :name), s(:str, "index_duplicate_blocks_on_duplicate_set_id"))))
83
+ node[3][2][1][1]
84
+ end
85
+
86
+ def end_file(filename)
87
+ @foreign_keys.keys.each do |table|
88
+ foreign_keys = @foreign_keys[table] || []
89
+ indexes = @indexes[table] || []
90
+ missing_indexes = foreign_keys - indexes
91
+ missing_indexes.each do |fkey|
92
+ add_error("Table '#{table}' is missing an index on the foreign key '#{fkey}'", filename, 1)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,18 @@
1
+ require 'roodi/checks/line_count_check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks a module to make sure the number of lines it has is under the specified limit.
6
+ #
7
+ # A module getting too large is a code smell that indicates it might be taking on too many
8
+ # responsibilities. It should probably be refactored into multiple smaller modules.
9
+ class ModuleLineCountCheck < LineCountCheck
10
+ DEFAULT_LINE_COUNT = 300
11
+
12
+ def initialize(options = {})
13
+ line_count = options['line_count'] || DEFAULT_LINE_COUNT
14
+ super([:module], line_count, 'Module')
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ require 'roodi/checks/name_check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks a module name to make sure it matches the specified pattern.
6
+ #
7
+ # Keeping to a consistent nameing convention makes your code easier to read.
8
+ class ModuleNameCheck < NameCheck
9
+ DEFAULT_PATTERN = /^[A-Z][a-zA-Z0-9]*$/
10
+
11
+ def initialize(options = {})
12
+ pattern = options['pattern'] || DEFAULT_PATTERN
13
+ super([:module], pattern, 'Module')
14
+ end
15
+
16
+ def find_name(node)
17
+ node[1].class == Symbol ? node[1] : node[1].last
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class NameCheck < Check
6
+ def initialize(interesting_nodes, pattern, message_prefix)
7
+ super()
8
+ @interesting_nodes = interesting_nodes
9
+ @pattern = pattern
10
+ @message_prefix = message_prefix
11
+ end
12
+
13
+ def interesting_nodes
14
+ @interesting_nodes
15
+ end
16
+
17
+ def evaluate_start(node)
18
+ name = find_name(node)
19
+ add_error "#{@message_prefix} name \"#{name}\" should match pattern #{@pattern.inspect}" unless name.to_s =~ @pattern
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,73 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class NpathComplexityCheck < Check
6
+ # , :when, :and, :or
7
+ MULTIPLYING_NODE_TYPES = [:if, :while, :until, :for, :case]
8
+ ADDING_NODE_TYPES = [:rescue]
9
+ COMPLEXITY_NODE_TYPES = MULTIPLYING_NODE_TYPES + ADDING_NODE_TYPES
10
+
11
+ def initialize(complexity)
12
+ super()
13
+ @complexity = complexity
14
+ @value_stack = []
15
+ @current_value = 1
16
+ end
17
+
18
+ def evalute_start_if(node)
19
+ push_value
20
+ end
21
+
22
+ def evalute_start_while(node)
23
+ push_value
24
+ end
25
+
26
+ def evalute_start_until(node)
27
+ push_value
28
+ end
29
+
30
+ def evalute_start_for(node)
31
+ push_value
32
+ end
33
+
34
+ def evalute_start_case(node)
35
+ push_value
36
+ end
37
+
38
+ def evalute_start_rescue(node)
39
+ push_value
40
+ end
41
+
42
+ MULTIPLYING_NODE_TYPES.each do |type|
43
+ define_method "evaluate_end_#{type}" do |node|
44
+ leave_multiplying_conditional
45
+ end
46
+ end
47
+
48
+ ADDING_NODE_TYPES.each do |type|
49
+ define_method "evaluate_end_#{type}" do |node|
50
+ leave_multiplying_conditional
51
+ end
52
+ end
53
+
54
+ protected
55
+
56
+ def push_value
57
+ @value_stack.push @current_value
58
+ @current_value = 1
59
+ end
60
+
61
+ def leave_multiplying_conditional
62
+ pop = @value_stack.pop
63
+ @current_value = (@current_value + 1) * pop
64
+ end
65
+
66
+ def leave_adding_conditional
67
+ pop = @value_stack.pop
68
+ puts "#{type}, so adding #{pop}"
69
+ @current_value = @current_value - 1 + pop
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,28 @@
1
+ require 'roodi/checks/npath_complexity_check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks Npath complexity of a method against a specified limit.
6
+ class NpathComplexityMethodCheck < NpathComplexityCheck
7
+ DEFAULT_COMPLEXITY = 8
8
+
9
+ def initialize(options = {})
10
+ complexity = options['complexity'] || DEFAULT_COMPLEXITY
11
+ super(complexity)
12
+ end
13
+
14
+ def interesting_nodes
15
+ [:defn] + COMPLEXITY_NODE_TYPES
16
+ end
17
+
18
+ def evaluate_start_defn(node)
19
+ @method_name = @node[1]
20
+ push_value
21
+ end
22
+
23
+ def evaluate_end_defn(node)
24
+ add_error "Method name \"#{@method_name}\" n-path complexity is #{@current_value}. It should be #{@complexity} or less." unless @current_value <= @complexity
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks a method to make sure the number of parameters it has is under the specified limit.
6
+ #
7
+ # A method taking too many parameters is a code smell that indicates it might be doing too
8
+ # much, or that the parameters should be grouped into one or more objects of their own. It
9
+ # probably needs some refactoring.
10
+ class ParameterNumberCheck < Check
11
+ DEFAULT_PARAMETER_COUNT = 5
12
+
13
+ def initialize(options = {})
14
+ super()
15
+ @parameter_count = options['parameter_count'] || DEFAULT_PARAMETER_COUNT
16
+ end
17
+
18
+ def interesting_nodes
19
+ [:defn]
20
+ end
21
+
22
+ def evaluate_start(node)
23
+ method_name = node[1]
24
+ arguments = node[2]
25
+ parameter_count = arguments.inject(-1) { |count, each| count = count + (each.class == Symbol ? 1 : 0) }
26
+ add_error "Method name \"#{method_name}\" has #{parameter_count} parameters. It should have #{@parameter_count} or less." unless parameter_count <= @parameter_count
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,18 @@
1
+ require 'roodi/checks/abc_metric_method_check'
2
+ require 'roodi/checks/assignment_in_conditional_check'
3
+ require 'roodi/checks/case_missing_else_check'
4
+ require 'roodi/checks/class_line_count_check'
5
+ require 'roodi/checks/class_name_check'
6
+ require 'roodi/checks/class_variable_check'
7
+ require 'roodi/checks/control_coupling_check'
8
+ require 'roodi/checks/cyclomatic_complexity_block_check'
9
+ require 'roodi/checks/cyclomatic_complexity_method_check'
10
+ require 'roodi/checks/empty_rescue_body_check'
11
+ require 'roodi/checks/for_loop_check'
12
+ require 'roodi/checks/method_line_count_check'
13
+ require 'roodi/checks/method_name_check'
14
+ require 'roodi/checks/missing_foreign_key_index_check'
15
+ require 'roodi/checks/module_line_count_check'
16
+ require 'roodi/checks/module_name_check'
17
+ require 'roodi/checks/npath_complexity_method_check'
18
+ require 'roodi/checks/parameter_number_check'
@@ -0,0 +1,26 @@
1
+ module Roodi
2
+ module Core
3
+ class CheckingVisitor
4
+ def initialize(*checks)
5
+ @checks ||= {}
6
+ checks.first.each do |check|
7
+ nodes = check.interesting_nodes
8
+ nodes.each do |node|
9
+ @checks[node] ||= []
10
+ @checks[node] << check
11
+ @checks[node].uniq!
12
+ end
13
+ end
14
+ end
15
+
16
+ def visit(node)
17
+ checks = @checks[node.node_type]
18
+ checks.each {|check| check.evaluate_node_start(node)} unless checks.nil?
19
+
20
+ node.visitable_children.each {|sexp| sexp.accept(self)}
21
+
22
+ checks.each {|check| check.evaluate_node_end(node)} unless checks.nil?
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ module Roodi
2
+ module Core
3
+ class Error
4
+ attr_reader :filename, :line_number, :message
5
+
6
+ def initialize(filename, line_number, message)
7
+ @filename = filename
8
+ @line_number = line_number
9
+ @message = message
10
+ end
11
+
12
+ def to_s
13
+ "#{@filename}:#{@line_number} - #{@message}"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'ruby_parser'
3
+
4
+ module Roodi
5
+ module Core
6
+ class Parser
7
+ def parse(content, filename)
8
+ silence_stream(STDERR) do
9
+ return silent_parse(content, filename)
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def silence_stream(stream)
16
+ old_stream = stream.dup
17
+ stream.reopen(RUBY_PLATFORM =~ /mswin/ ? 'NUL:' : '/dev/null')
18
+ stream.sync = true
19
+ yield
20
+ ensure
21
+ stream.reopen(old_stream)
22
+ end
23
+
24
+ def silent_parse(content, filename)
25
+ @parser ||= RubyParser.new
26
+ @parser.parse(content, filename)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,80 @@
1
+ require 'pp'
2
+ require 'yaml'
3
+
4
+ require 'roodi/core/checking_visitor'
5
+ require 'roodi/core/parser'
6
+ require 'roodi/core/visitable_sexp'
7
+
8
+ module Roodi
9
+ module Core
10
+ class Runner
11
+ DEFAULT_CONFIG = File.join(File.dirname(__FILE__), "..", "..", "..", "roodi.yml")
12
+
13
+ attr_writer :config
14
+
15
+ def initialize(*checks)
16
+ @config = DEFAULT_CONFIG
17
+ @checks = checks unless checks.empty?
18
+ @parser = Parser.new
19
+ end
20
+
21
+ def check(filename, content)
22
+ @checks ||= load_checks
23
+ @checker ||= CheckingVisitor.new(@checks)
24
+ @checks.each {|check| check.start_file(filename)}
25
+ node = parse(filename, content)
26
+ node.accept(@checker) if node
27
+ @checks.each {|check| check.end_file(filename)}
28
+ end
29
+
30
+ def check_content(content, filename = "dummy-file.rb")
31
+ check(filename, content)
32
+ end
33
+
34
+ def check_file(filename)
35
+ check(filename, File.read(filename))
36
+ end
37
+
38
+ def print(filename, content)
39
+ node = @parser.parse(content, filename)
40
+ puts "Line: #{node.line}"
41
+ pp node
42
+ end
43
+
44
+ def print_content(content)
45
+ print("dummy-file.rb", content)
46
+ end
47
+
48
+ def print_file(filename)
49
+ print(filename, File.read(filename))
50
+ end
51
+
52
+ def errors
53
+ @checks ||= []
54
+ all_errors = @checks.collect {|check| check.errors}
55
+ all_errors.flatten
56
+ end
57
+
58
+ private
59
+
60
+ def parse(filename, content)
61
+ begin
62
+ @parser.parse(content, filename)
63
+ rescue Exception => e
64
+ puts "#{filename} looks like it's not a valid Ruby file. Skipping..." if ENV["ROODI_DEBUG"]
65
+ nil
66
+ end
67
+ end
68
+
69
+ def load_checks
70
+ check_objects = []
71
+ checks = YAML.load_file @config
72
+ checks.each do |check|
73
+ klass = eval("Roodi::Checks::#{check[0]}")
74
+ check_objects << (check[1].empty? ? klass.new : klass.new(check[1]))
75
+ end
76
+ check_objects
77
+ end
78
+ end
79
+ end
80
+ end