roodi1.9 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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