excellent 1.5.4

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 (106) hide show
  1. data/History.txt +69 -0
  2. data/README.rdoc +72 -0
  3. data/VERSION.yml +4 -0
  4. data/bin/excellent +34 -0
  5. data/lib/simplabs/excellent.rb +16 -0
  6. data/lib/simplabs/excellent/checks.rb +33 -0
  7. data/lib/simplabs/excellent/checks/abc_metric_method_check.rb +43 -0
  8. data/lib/simplabs/excellent/checks/assignment_in_conditional_check.rb +39 -0
  9. data/lib/simplabs/excellent/checks/base.rb +62 -0
  10. data/lib/simplabs/excellent/checks/case_missing_else_check.rb +34 -0
  11. data/lib/simplabs/excellent/checks/class_line_count_check.rb +36 -0
  12. data/lib/simplabs/excellent/checks/class_name_check.rb +38 -0
  13. data/lib/simplabs/excellent/checks/control_coupling_check.rb +35 -0
  14. data/lib/simplabs/excellent/checks/cyclomatic_complexity_block_check.rb +48 -0
  15. data/lib/simplabs/excellent/checks/cyclomatic_complexity_check.rb +23 -0
  16. data/lib/simplabs/excellent/checks/cyclomatic_complexity_method_check.rb +48 -0
  17. data/lib/simplabs/excellent/checks/empty_rescue_body_check.rb +31 -0
  18. data/lib/simplabs/excellent/checks/flog_block_check.rb +40 -0
  19. data/lib/simplabs/excellent/checks/flog_check.rb +27 -0
  20. data/lib/simplabs/excellent/checks/flog_class_check.rb +40 -0
  21. data/lib/simplabs/excellent/checks/flog_method_check.rb +40 -0
  22. data/lib/simplabs/excellent/checks/for_loop_check.rb +42 -0
  23. data/lib/simplabs/excellent/checks/global_variable_check.rb +33 -0
  24. data/lib/simplabs/excellent/checks/line_count_check.rb +27 -0
  25. data/lib/simplabs/excellent/checks/method_line_count_check.rb +36 -0
  26. data/lib/simplabs/excellent/checks/method_name_check.rb +38 -0
  27. data/lib/simplabs/excellent/checks/module_line_count_check.rb +36 -0
  28. data/lib/simplabs/excellent/checks/module_name_check.rb +38 -0
  29. data/lib/simplabs/excellent/checks/name_check.rb +27 -0
  30. data/lib/simplabs/excellent/checks/nested_iterators_check.rb +34 -0
  31. data/lib/simplabs/excellent/checks/parameter_number_check.rb +38 -0
  32. data/lib/simplabs/excellent/checks/rails.rb +22 -0
  33. data/lib/simplabs/excellent/checks/rails/attr_accessible_check.rb +38 -0
  34. data/lib/simplabs/excellent/checks/rails/attr_protected_check.rb +39 -0
  35. data/lib/simplabs/excellent/checks/rails/custom_initialize_method_check.rb +37 -0
  36. data/lib/simplabs/excellent/checks/rails/instance_var_in_partial_check.rb +37 -0
  37. data/lib/simplabs/excellent/checks/rails/params_hash_in_view_check.rb +38 -0
  38. data/lib/simplabs/excellent/checks/rails/session_hash_in_view_check.rb +38 -0
  39. data/lib/simplabs/excellent/checks/rails/validations_check.rb +36 -0
  40. data/lib/simplabs/excellent/checks/singleton_variable_check.rb +33 -0
  41. data/lib/simplabs/excellent/command_line_runner.rb +37 -0
  42. data/lib/simplabs/excellent/extensions/sexp.rb +21 -0
  43. data/lib/simplabs/excellent/extensions/string.rb +28 -0
  44. data/lib/simplabs/excellent/formatters.rb +13 -0
  45. data/lib/simplabs/excellent/formatters/base.rb +49 -0
  46. data/lib/simplabs/excellent/formatters/html.rb +153 -0
  47. data/lib/simplabs/excellent/formatters/text.rb +40 -0
  48. data/lib/simplabs/excellent/parsing.rb +10 -0
  49. data/lib/simplabs/excellent/parsing/abc_measure.rb +52 -0
  50. data/lib/simplabs/excellent/parsing/block_context.rb +43 -0
  51. data/lib/simplabs/excellent/parsing/call_context.rb +52 -0
  52. data/lib/simplabs/excellent/parsing/case_context.rb +31 -0
  53. data/lib/simplabs/excellent/parsing/class_context.rb +99 -0
  54. data/lib/simplabs/excellent/parsing/code_processor.rb +165 -0
  55. data/lib/simplabs/excellent/parsing/conditional_context.rb +25 -0
  56. data/lib/simplabs/excellent/parsing/cvar_context.rb +28 -0
  57. data/lib/simplabs/excellent/parsing/cyclomatic_complexity_measure.rb +73 -0
  58. data/lib/simplabs/excellent/parsing/flog_measure.rb +192 -0
  59. data/lib/simplabs/excellent/parsing/for_loop_context.rb +15 -0
  60. data/lib/simplabs/excellent/parsing/gvar_context.rb +21 -0
  61. data/lib/simplabs/excellent/parsing/if_context.rb +38 -0
  62. data/lib/simplabs/excellent/parsing/ivar_context.rb +32 -0
  63. data/lib/simplabs/excellent/parsing/method_context.rb +50 -0
  64. data/lib/simplabs/excellent/parsing/module_context.rb +29 -0
  65. data/lib/simplabs/excellent/parsing/parser.rb +35 -0
  66. data/lib/simplabs/excellent/parsing/resbody_context.rb +39 -0
  67. data/lib/simplabs/excellent/parsing/scopeable.rb +34 -0
  68. data/lib/simplabs/excellent/parsing/sexp_context.rb +125 -0
  69. data/lib/simplabs/excellent/parsing/singleton_method_context.rb +55 -0
  70. data/lib/simplabs/excellent/parsing/until_context.rb +24 -0
  71. data/lib/simplabs/excellent/parsing/while_context.rb +24 -0
  72. data/lib/simplabs/excellent/rake.rb +1 -0
  73. data/lib/simplabs/excellent/rake/excellent_task.rb +61 -0
  74. data/lib/simplabs/excellent/runner.rb +143 -0
  75. data/lib/simplabs/excellent/warning.rb +53 -0
  76. data/spec/checks/abc_metric_method_check_spec.rb +122 -0
  77. data/spec/checks/assignment_in_conditional_check_spec.rb +90 -0
  78. data/spec/checks/case_missing_else_check_spec.rb +42 -0
  79. data/spec/checks/class_line_count_check_spec.rb +62 -0
  80. data/spec/checks/class_name_check_spec.rb +48 -0
  81. data/spec/checks/control_coupling_check_spec.rb +103 -0
  82. data/spec/checks/cyclomatic_complexity_block_check_spec.rb +47 -0
  83. data/spec/checks/cyclomatic_complexity_method_check_spec.rb +210 -0
  84. data/spec/checks/empty_rescue_body_check_spec.rb +170 -0
  85. data/spec/checks/flog_block_check_spec.rb +28 -0
  86. data/spec/checks/flog_class_check_spec.rb +28 -0
  87. data/spec/checks/flog_method_check_spec.rb +46 -0
  88. data/spec/checks/for_loop_check_spec.rb +52 -0
  89. data/spec/checks/global_variable_check_spec.rb +66 -0
  90. data/spec/checks/method_line_count_check_spec.rb +49 -0
  91. data/spec/checks/method_name_check_spec.rb +112 -0
  92. data/spec/checks/module_line_count_check_spec.rb +48 -0
  93. data/spec/checks/module_name_check_spec.rb +61 -0
  94. data/spec/checks/nested_iterators_check_spec.rb +44 -0
  95. data/spec/checks/parameter_number_check_spec.rb +97 -0
  96. data/spec/checks/rails/attr_accessible_check_spec.rb +79 -0
  97. data/spec/checks/rails/attr_protected_check_spec.rb +77 -0
  98. data/spec/checks/rails/custom_initialize_method_check_spec.rb +58 -0
  99. data/spec/checks/rails/instance_var_in_partial_check_spec.rb +40 -0
  100. data/spec/checks/rails/params_hash_in_view_check_spec.rb +40 -0
  101. data/spec/checks/rails/session_hash_in_view_check_spec.rb +40 -0
  102. data/spec/checks/rails/validations_check_spec.rb +81 -0
  103. data/spec/checks/singleton_variable_check_spec.rb +66 -0
  104. data/spec/extensions/string_spec.rb +13 -0
  105. data/spec/spec_helper.rb +13 -0
  106. metadata +189 -0
@@ -0,0 +1,29 @@
1
+ require 'simplabs/excellent/parsing/scopeable'
2
+
3
+ module Simplabs
4
+
5
+ module Excellent
6
+
7
+ module Parsing
8
+
9
+ class ModuleContext < SexpContext #:nodoc:
10
+
11
+ include Scopeable
12
+
13
+ attr_reader :methods
14
+ attr_reader :line_count
15
+
16
+ def initialize(exp, parent)
17
+ super
18
+ @name, @full_name = get_names
19
+ @methods = []
20
+ @line_count = count_lines
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,35 @@
1
+ require 'rubygems'
2
+ require 'ruby_parser'
3
+ require 'facets'
4
+ require 'erb'
5
+
6
+ module Simplabs
7
+
8
+ module Excellent
9
+
10
+ module Parsing
11
+
12
+ class Parser #:nodoc:
13
+
14
+ def parse(content, filename)
15
+ return silent_parse(content, filename)
16
+ rescue Exception
17
+ #continue on errors
18
+ end
19
+
20
+ private
21
+
22
+ def silent_parse(content, filename)
23
+ @parser ||= RubyParser.new
24
+ content = ::ERB.new(content, nil, '-').src if filename =~ /\.erb$/
25
+ sexp = @parser.parse(content, filename)
26
+ sexp
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,39 @@
1
+ module Simplabs
2
+
3
+ module Excellent
4
+
5
+ module Parsing
6
+
7
+ class ResbodyContext < SexpContext #:nodoc:
8
+
9
+ STATEMENT_NODES = [:fcall, :return, :attrasgn, :vcall, :call, :str, :lit, :hash, :false, :true, :nil]
10
+
11
+ def initialize(exp, parent)
12
+ super
13
+ @contains_statements = contains_statements?
14
+ end
15
+
16
+ def has_statements?
17
+ @contains_statements
18
+ end
19
+
20
+ private
21
+
22
+ def contains_statements?(exp = @exp)
23
+ return true if STATEMENT_NODES.include?(exp.node_type)
24
+ return true if assigning_other_than_exception_to_local_variable?(exp)
25
+ return true if (exp[1][0] == :array && exp[2][0] == :array rescue false)
26
+ return true if exp.children.any? { |child| contains_statements?(child) }
27
+ end
28
+
29
+ def assigning_other_than_exception_to_local_variable?(exp)
30
+ exp.node_type == :lasgn && exp[2].to_a != [:gvar, :$!]
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,34 @@
1
+ module Simplabs
2
+
3
+ module Excellent
4
+
5
+ module Parsing
6
+
7
+ module Scopeable #:nodoc:
8
+
9
+ private
10
+
11
+ def get_names
12
+ if @exp[1].is_a?(Sexp)
13
+ name = @exp[1].pop.to_s.strip
14
+ [name, "#{extract_prefixes}#{name}"]
15
+ else
16
+ [@exp[1].to_s, nil]
17
+ end
18
+ end
19
+
20
+ def extract_prefixes(exp = @exp[1].deep_clone, prefix = '')
21
+ prefix = "#{exp.pop}::#{prefix}" if exp.last.is_a?(Symbol)
22
+ if exp.last.is_a?(Sexp)
23
+ prefix = extract_prefixes(exp.last, prefix)
24
+ end
25
+ prefix
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,125 @@
1
+ module Simplabs
2
+
3
+ module Excellent
4
+
5
+ module Parsing
6
+
7
+ # For most nodes the Excellent processor processes, it will create the corresponding context that contains meta information of the processed
8
+ # node. This is the base class for all these contexts.
9
+ #
10
+ # === Example
11
+ #
12
+ # For a method like the following:
13
+ #
14
+ # module Shop
15
+ # class Basket
16
+ # def buy_product(product)
17
+ # other_method
18
+ # end
19
+ # end
20
+ # end
21
+ #
22
+ # four context will be generated:
23
+ #
24
+ # ModuleContext
25
+ # name: 'Shop'
26
+ # full_name: 'Shop'
27
+ # parent: nil
28
+ # ClassContext
29
+ # name: 'Basket'
30
+ # full_name: 'Shop::Basket'
31
+ # parent: ModuleContext
32
+ # MethodContext
33
+ # name: 'buy_product'
34
+ # full_name: 'Shop::Basket#buy_product'
35
+ # parent: ClassContext
36
+ # parameters: [:product]
37
+ # CallContext (other_method)
38
+ # name: nil
39
+ # full_name: nil
40
+ # parent: MethodContext
41
+ # method: :other_method
42
+ #
43
+ # === Custom Processors
44
+ #
45
+ # The Excelent processor will also invoke custom processor methods on the contexts if they are defined. To process call nodes in the context for
46
+ # example, you could simply define a +process_call+ method in the context that will be invoked with each call Sexp (S-expression, see
47
+ # http://en.wikipedia.org/wiki/S_expression) that is processed by the Excellent processor.
48
+ #
49
+ # def process_call(exp)
50
+ # super
51
+ # do_something()
52
+ # end
53
+ #
54
+ # Custom <b>processor methods must always call +super+</b> since there might be several processor methods defined in several modules that are in the
55
+ # included in the context and all of these have to be invoked. Also <b>processor methods must not modify the passed Sexp</b> since other processor
56
+ # methods also need the complete Sexp. If you have to modify the Sexp in a processor method, deep clone it:
57
+ #
58
+ # exp = exp.deep_clone
59
+ #
60
+ class SexpContext
61
+
62
+ # The parent context
63
+ attr_reader :parent
64
+
65
+ # The name of the code fragment the context is bound to (e.g. 'User' for a +class+)
66
+ attr_reader :name
67
+
68
+ # The file the code fragment was read from
69
+ attr_reader :file
70
+
71
+ # The line the code fragment is located at
72
+ attr_reader :line
73
+
74
+ # Initializes a SexpContext.
75
+ #
76
+ # Always call +super+ in inherited custom contexts!
77
+ #
78
+ # === Parameters
79
+ #
80
+ # * <tt>exp</tt> - The Sexp (S-expression, see http://en.wikipedia.org/wiki/S_expression) the context is created for.
81
+ # * <tt>parent</tt> - The parent context.
82
+ def initialize(exp, parent = nil)
83
+ @exp = exp
84
+ @parent = parent
85
+ @file = exp.file
86
+ @line = exp.line
87
+ @full_name = nil
88
+ end
89
+
90
+ # Gets the full name of the code fragment the context is bound to. For a method +name+ might be '+add_product+' while +full_name+ might be
91
+ # 'Basket#add_product'.
92
+ def full_name
93
+ return @full_name if @full_name
94
+ return @name if @parent.blank?
95
+ "#{@parent.full_name}::#{@name}"
96
+ end
97
+
98
+ def method_missing(method, *args) #:nodoc:
99
+ return if method.to_s =~ /^process_[a-zA-Z0-9_]+$/
100
+ super
101
+ end
102
+
103
+ private
104
+
105
+ def count_lines(node = @exp, line_numbers = [])
106
+ count = 0
107
+ line_numbers << node.line
108
+ node.children.each { |child| count += count_lines(child, line_numbers) }
109
+ line_numbers.uniq.length
110
+ end
111
+
112
+ def has_assignment?(exp = @exp[1])
113
+ found_assignment = false
114
+ found_assignment = found_assignment || exp.node_type == :lasgn
115
+ exp.children.each { |child| found_assignment = found_assignment || has_assignment?(child) }
116
+ found_assignment
117
+ end
118
+
119
+ end
120
+
121
+ end
122
+
123
+ end
124
+
125
+ end
@@ -0,0 +1,55 @@
1
+ require 'simplabs/excellent/parsing/cyclomatic_complexity_measure'
2
+ require 'simplabs/excellent/parsing/abc_measure'
3
+
4
+ module Simplabs
5
+
6
+ module Excellent
7
+
8
+ module Parsing
9
+
10
+ class SingletonMethodContext < MethodContext #:nodoc:
11
+
12
+ include CyclomaticComplexityMeasure
13
+ include AbcMeasure
14
+
15
+ attr_reader :parameters
16
+ attr_reader :calls
17
+
18
+ def initialize(exp, parent)
19
+ super
20
+ @name = exp[2].to_s
21
+ @full_name = get_full_name
22
+ @calls = Hash.new(0)
23
+ end
24
+
25
+ def full_name
26
+ return @full_name if @full_name
27
+ parent = @parent.is_a?(BlockContext) ? @parent.parent : @parent
28
+ return @name if parent.blank?
29
+ "#{parent.full_name}.#{@name}"
30
+ end
31
+
32
+ def record_call_to(exp)
33
+ @calls[exp] += 1
34
+ end
35
+
36
+ private
37
+
38
+ def get_full_name
39
+ if @exp[1].is_a?(Sexp)
40
+ if @exp[1].node_type == :call
41
+ return "#{@exp[1][2]}.#{@name}"
42
+ elsif @exp[1].node_type == :const
43
+ return "#{@exp[1][1]}.#{@name}"
44
+ end
45
+ end
46
+ nil
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,24 @@
1
+ module Simplabs
2
+
3
+ module Excellent
4
+
5
+ module Parsing
6
+
7
+ class UntilContext < SexpContext #:nodoc:
8
+
9
+ def initialize(exp, parent)
10
+ super
11
+ @contains_assignment = has_assignment?
12
+ end
13
+
14
+ def tests_assignment?
15
+ @contains_assignment
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,24 @@
1
+ module Simplabs
2
+
3
+ module Excellent
4
+
5
+ module Parsing
6
+
7
+ class WhileContext < SexpContext #:nodoc:
8
+
9
+ def initialize(exp, parent)
10
+ super
11
+ @contains_assignment = has_assignment?
12
+ end
13
+
14
+ def tests_assignment?
15
+ @contains_assignment
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1 @@
1
+ require 'simplabs/excellent/rake/excellent_task'
@@ -0,0 +1,61 @@
1
+ require 'rake'
2
+ require 'simplabs/excellent'
3
+
4
+ module Simplabs
5
+
6
+ module Excellent
7
+
8
+ module Rake #:nodoc:
9
+
10
+ # A special rake task for Excellent.
11
+ class ExcellentTask < ::Rake::TaskLib
12
+
13
+ # The Name of the task, defaults to <tt>:excellent</tt>.
14
+ attr_accessor :name
15
+
16
+ # Specifies whether to output HTML; defaults to false. Assign a file name to output HTML to that file.
17
+ attr_accessor :html
18
+
19
+ # The paths to process (specify file names or directories; will recursively process all ruby files if a directory is given).
20
+ attr_accessor :paths
21
+
22
+ # Initializes an ExcellentTask with the name +name+.
23
+ def initialize(name = :excellent)
24
+ @name = name
25
+ @paths = nil || []
26
+ @html = false
27
+ yield self if block_given?
28
+ define
29
+ end
30
+
31
+ def paths=(paths) #:nodoc:
32
+ if paths.is_a?(String)
33
+ @paths = [paths]
34
+ elsif paths.is_a?(Array)
35
+ @paths = paths
36
+ else
37
+ raise ArgumentError.new('Specify paths either as a String or as an Array!')
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def define
44
+ unless ::Rake.application.last_comment
45
+ desc 'Analyse the code with Excellent'
46
+ end
47
+ task name do
48
+ paths = @paths.join(' ')
49
+ format = @html ? " html:#{@html}" : ''
50
+ system("excellent#{format} #{paths}")
51
+ $stdout.puts("\nWrote Excellent result to #{@html}\n\n") if @html
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,143 @@
1
+ require 'pp'
2
+ require 'yaml'
3
+ require 'simplabs/excellent/parsing/parser'
4
+ require 'simplabs/excellent/parsing/code_processor'
5
+
6
+ module Simplabs
7
+
8
+ module Excellent
9
+
10
+ # The Runner is the interface to invoke parsing and processing of source code. You can pass either a String containing the code to process or the
11
+ # name of a file to read the code to process from.
12
+ class Runner
13
+
14
+ DEFAULT_CONFIG = {
15
+ :AssignmentInConditionalCheck => { },
16
+ :CaseMissingElseCheck => { },
17
+ :ClassLineCountCheck => { :threshold => 300 },
18
+ :ClassNameCheck => { :pattern => /^[A-Z][a-zA-Z0-9]*$/ },
19
+ :SingletonVariableCheck => { },
20
+ :GlobalVariableCheck => { },
21
+ :EmptyRescueBodyCheck => { },
22
+ :MethodLineCountCheck => { :line_count => 20 },
23
+ :MethodNameCheck => { :pattern => /^[_a-z<>=\[|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/ },
24
+ :ModuleLineCountCheck => { :line_count => 300 },
25
+ :ModuleNameCheck => { :pattern => /^[A-Z][a-zA-Z0-9]*$/ },
26
+ :ParameterNumberCheck => { :parameter_count => 3 },
27
+ :'Rails::AttrProtectedCheck' => { },
28
+ :'Rails::AttrAccessibleCheck' => { },
29
+ :'Rails::InstanceVarInPartialCheck' => { },
30
+ :'Rails::ValidationsCheck' => { },
31
+ :'Rails::ParamsHashInViewCheck' => { },
32
+ :'Rails::SessionHashInViewCheck' => { },
33
+ :'Rails::CustomInitializeMethodCheck' => { }
34
+ }
35
+
36
+ attr_accessor :config #:nodoc:
37
+
38
+ # Initializes a Runner
39
+ #
40
+ # ==== Parameters
41
+ #
42
+ # * <tt>checks</tt> - The checks to apply - pass instances of the various check classes. If no checks are specified, all checks will be applied.
43
+ def initialize(*checks)
44
+ @config = DEFAULT_CONFIG
45
+ @checks = checks unless checks.empty?
46
+ @parser = Parsing::Parser.new
47
+ end
48
+
49
+ # Processes the +code+ and sets the file name of the warning to +filename+
50
+ #
51
+ # ==== Parameters
52
+ #
53
+ # * <tt>filename</tt> - The name of the file the code was read from.
54
+ # * <tt>code</tt> - The code to process (String).
55
+ def check(filename, code)
56
+ @checks ||= load_checks
57
+ @processor ||= Parsing::CodeProcessor.new(@checks)
58
+ node = parse(filename, code)
59
+ @processor.process(node)
60
+ end
61
+
62
+ # Processes the +code+, setting the file name of the warnings to '+dummy-file.rb+'
63
+ #
64
+ # ==== Parameters
65
+ #
66
+ # * <tt>code</tt> - The code to process (String).
67
+ def check_code(code)
68
+ check('dummy-file.rb', code)
69
+ end
70
+
71
+ # Processes the file +filename+. The code will be read from the file.
72
+ #
73
+ # ==== Parameters
74
+ #
75
+ # * <tt>filename</tt> - The name of the file to read the code from.
76
+ def check_file(filename)
77
+ check(filename, File.read(filename))
78
+ end
79
+
80
+ # Processes the passed +paths+
81
+ #
82
+ # ==== Parameters
83
+ #
84
+ # * <tt>paths</tt> - The paths to process (specify file names or directories; will recursively process all ruby files if a directory is given).
85
+ # * <tt>formatter</tt> - The formatter to use. If a formatter is specified, its +start+, +file+, +warning+ and +end+ methods will be called
86
+ def check_paths(paths, formatter = nil)
87
+ formatter.start if formatter
88
+ collect_files(paths).each do |path|
89
+ check_file(path)
90
+ format_file_and_warnings(formatter, path) if formatter
91
+ end
92
+ formatter.end if formatter
93
+ end
94
+
95
+ # Gets the warnings that were produced by the checks.
96
+ def warnings
97
+ @checks ||= []
98
+ @checks.collect { |check| check.warnings }.flatten
99
+ end
100
+
101
+ private
102
+
103
+ def parse(filename, code)
104
+ @parser.parse(code, filename)
105
+ end
106
+
107
+ def load_checks
108
+ check_objects = []
109
+ DEFAULT_CONFIG.each_pair do |key, value|
110
+ klass = eval("Simplabs::Excellent::Checks::#{key.to_s}")
111
+ check_objects << (value.empty? ? klass.new : klass.new(value))
112
+ end
113
+ check_objects
114
+ end
115
+
116
+ def collect_files(paths)
117
+ files = []
118
+ paths.each do |path|
119
+ if File.file?(path)
120
+ files << path
121
+ elsif File.directory?(path)
122
+ files += Dir.glob(File.join(path, '**/*.{rb,erb}'))
123
+ else
124
+ raise ArgumentError.new("#{path} is neither a File nor a directory!")
125
+ end
126
+ end
127
+ files
128
+ end
129
+
130
+ def format_file_and_warnings(formatter, filename)
131
+ warnings = @checks.map { |check| check.warnings_for(filename) }.flatten
132
+ if warnings.length > 0
133
+ formatter.file(filename) do |formatter|
134
+ warnings.sort { |x, y| x.line_number <=> y.line_number }.each { |warning| formatter.warning(warning) }
135
+ end
136
+ end
137
+ end
138
+
139
+ end
140
+
141
+ end
142
+
143
+ end