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.
- data/History.txt +69 -0
- data/README.rdoc +72 -0
- data/VERSION.yml +4 -0
- data/bin/excellent +34 -0
- data/lib/simplabs/excellent.rb +16 -0
- data/lib/simplabs/excellent/checks.rb +33 -0
- data/lib/simplabs/excellent/checks/abc_metric_method_check.rb +43 -0
- data/lib/simplabs/excellent/checks/assignment_in_conditional_check.rb +39 -0
- data/lib/simplabs/excellent/checks/base.rb +62 -0
- data/lib/simplabs/excellent/checks/case_missing_else_check.rb +34 -0
- data/lib/simplabs/excellent/checks/class_line_count_check.rb +36 -0
- data/lib/simplabs/excellent/checks/class_name_check.rb +38 -0
- data/lib/simplabs/excellent/checks/control_coupling_check.rb +35 -0
- data/lib/simplabs/excellent/checks/cyclomatic_complexity_block_check.rb +48 -0
- data/lib/simplabs/excellent/checks/cyclomatic_complexity_check.rb +23 -0
- data/lib/simplabs/excellent/checks/cyclomatic_complexity_method_check.rb +48 -0
- data/lib/simplabs/excellent/checks/empty_rescue_body_check.rb +31 -0
- data/lib/simplabs/excellent/checks/flog_block_check.rb +40 -0
- data/lib/simplabs/excellent/checks/flog_check.rb +27 -0
- data/lib/simplabs/excellent/checks/flog_class_check.rb +40 -0
- data/lib/simplabs/excellent/checks/flog_method_check.rb +40 -0
- data/lib/simplabs/excellent/checks/for_loop_check.rb +42 -0
- data/lib/simplabs/excellent/checks/global_variable_check.rb +33 -0
- data/lib/simplabs/excellent/checks/line_count_check.rb +27 -0
- data/lib/simplabs/excellent/checks/method_line_count_check.rb +36 -0
- data/lib/simplabs/excellent/checks/method_name_check.rb +38 -0
- data/lib/simplabs/excellent/checks/module_line_count_check.rb +36 -0
- data/lib/simplabs/excellent/checks/module_name_check.rb +38 -0
- data/lib/simplabs/excellent/checks/name_check.rb +27 -0
- data/lib/simplabs/excellent/checks/nested_iterators_check.rb +34 -0
- data/lib/simplabs/excellent/checks/parameter_number_check.rb +38 -0
- data/lib/simplabs/excellent/checks/rails.rb +22 -0
- data/lib/simplabs/excellent/checks/rails/attr_accessible_check.rb +38 -0
- data/lib/simplabs/excellent/checks/rails/attr_protected_check.rb +39 -0
- data/lib/simplabs/excellent/checks/rails/custom_initialize_method_check.rb +37 -0
- data/lib/simplabs/excellent/checks/rails/instance_var_in_partial_check.rb +37 -0
- data/lib/simplabs/excellent/checks/rails/params_hash_in_view_check.rb +38 -0
- data/lib/simplabs/excellent/checks/rails/session_hash_in_view_check.rb +38 -0
- data/lib/simplabs/excellent/checks/rails/validations_check.rb +36 -0
- data/lib/simplabs/excellent/checks/singleton_variable_check.rb +33 -0
- data/lib/simplabs/excellent/command_line_runner.rb +37 -0
- data/lib/simplabs/excellent/extensions/sexp.rb +21 -0
- data/lib/simplabs/excellent/extensions/string.rb +28 -0
- data/lib/simplabs/excellent/formatters.rb +13 -0
- data/lib/simplabs/excellent/formatters/base.rb +49 -0
- data/lib/simplabs/excellent/formatters/html.rb +153 -0
- data/lib/simplabs/excellent/formatters/text.rb +40 -0
- data/lib/simplabs/excellent/parsing.rb +10 -0
- data/lib/simplabs/excellent/parsing/abc_measure.rb +52 -0
- data/lib/simplabs/excellent/parsing/block_context.rb +43 -0
- data/lib/simplabs/excellent/parsing/call_context.rb +52 -0
- data/lib/simplabs/excellent/parsing/case_context.rb +31 -0
- data/lib/simplabs/excellent/parsing/class_context.rb +99 -0
- data/lib/simplabs/excellent/parsing/code_processor.rb +165 -0
- data/lib/simplabs/excellent/parsing/conditional_context.rb +25 -0
- data/lib/simplabs/excellent/parsing/cvar_context.rb +28 -0
- data/lib/simplabs/excellent/parsing/cyclomatic_complexity_measure.rb +73 -0
- data/lib/simplabs/excellent/parsing/flog_measure.rb +192 -0
- data/lib/simplabs/excellent/parsing/for_loop_context.rb +15 -0
- data/lib/simplabs/excellent/parsing/gvar_context.rb +21 -0
- data/lib/simplabs/excellent/parsing/if_context.rb +38 -0
- data/lib/simplabs/excellent/parsing/ivar_context.rb +32 -0
- data/lib/simplabs/excellent/parsing/method_context.rb +50 -0
- data/lib/simplabs/excellent/parsing/module_context.rb +29 -0
- data/lib/simplabs/excellent/parsing/parser.rb +35 -0
- data/lib/simplabs/excellent/parsing/resbody_context.rb +39 -0
- data/lib/simplabs/excellent/parsing/scopeable.rb +34 -0
- data/lib/simplabs/excellent/parsing/sexp_context.rb +125 -0
- data/lib/simplabs/excellent/parsing/singleton_method_context.rb +55 -0
- data/lib/simplabs/excellent/parsing/until_context.rb +24 -0
- data/lib/simplabs/excellent/parsing/while_context.rb +24 -0
- data/lib/simplabs/excellent/rake.rb +1 -0
- data/lib/simplabs/excellent/rake/excellent_task.rb +61 -0
- data/lib/simplabs/excellent/runner.rb +143 -0
- data/lib/simplabs/excellent/warning.rb +53 -0
- data/spec/checks/abc_metric_method_check_spec.rb +122 -0
- data/spec/checks/assignment_in_conditional_check_spec.rb +90 -0
- data/spec/checks/case_missing_else_check_spec.rb +42 -0
- data/spec/checks/class_line_count_check_spec.rb +62 -0
- data/spec/checks/class_name_check_spec.rb +48 -0
- data/spec/checks/control_coupling_check_spec.rb +103 -0
- data/spec/checks/cyclomatic_complexity_block_check_spec.rb +47 -0
- data/spec/checks/cyclomatic_complexity_method_check_spec.rb +210 -0
- data/spec/checks/empty_rescue_body_check_spec.rb +170 -0
- data/spec/checks/flog_block_check_spec.rb +28 -0
- data/spec/checks/flog_class_check_spec.rb +28 -0
- data/spec/checks/flog_method_check_spec.rb +46 -0
- data/spec/checks/for_loop_check_spec.rb +52 -0
- data/spec/checks/global_variable_check_spec.rb +66 -0
- data/spec/checks/method_line_count_check_spec.rb +49 -0
- data/spec/checks/method_name_check_spec.rb +112 -0
- data/spec/checks/module_line_count_check_spec.rb +48 -0
- data/spec/checks/module_name_check_spec.rb +61 -0
- data/spec/checks/nested_iterators_check_spec.rb +44 -0
- data/spec/checks/parameter_number_check_spec.rb +97 -0
- data/spec/checks/rails/attr_accessible_check_spec.rb +79 -0
- data/spec/checks/rails/attr_protected_check_spec.rb +77 -0
- data/spec/checks/rails/custom_initialize_method_check_spec.rb +58 -0
- data/spec/checks/rails/instance_var_in_partial_check_spec.rb +40 -0
- data/spec/checks/rails/params_hash_in_view_check_spec.rb +40 -0
- data/spec/checks/rails/session_hash_in_view_check_spec.rb +40 -0
- data/spec/checks/rails/validations_check_spec.rb +81 -0
- data/spec/checks/singleton_variable_check_spec.rb +66 -0
- data/spec/extensions/string_spec.rb +13 -0
- data/spec/spec_helper.rb +13 -0
- 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
|