kwala 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +62 -0
- data/bin/kwala +39 -0
- data/lib/kwala.rb +59 -0
- data/lib/kwala/actions/code_change.rb +128 -0
- data/lib/kwala/actions/code_coverage.rb +303 -0
- data/lib/kwala/actions/code_duplication.rb +222 -0
- data/lib/kwala/actions/code_formatting.rb +358 -0
- data/lib/kwala/actions/cycle_detection.rb +118 -0
- data/lib/kwala/actions/cyclomatic_complexity.rb +159 -0
- data/lib/kwala/actions/dead_code.rb +68 -0
- data/lib/kwala/actions/executable_files_check.rb +111 -0
- data/lib/kwala/actions/flagged_comments.rb +128 -0
- data/lib/kwala/actions/gem_plugin.rb +31 -0
- data/lib/kwala/actions/loc_count.rb +153 -0
- data/lib/kwala/actions/multi_ruby_unit_test.rb +62 -0
- data/lib/kwala/actions/outside_links.rb +85 -0
- data/lib/kwala/actions/rails_migrate.rb +62 -0
- data/lib/kwala/actions/strange_requires.rb +106 -0
- data/lib/kwala/actions/syntax_check.rb +130 -0
- data/lib/kwala/actions/unit_test.rb +368 -0
- data/lib/kwala/build_action.rb +88 -0
- data/lib/kwala/build_context.rb +49 -0
- data/lib/kwala/command_line_runner.rb +184 -0
- data/lib/kwala/ext/demos.jar +0 -0
- data/lib/kwala/ext/pmd.jar +0 -0
- data/lib/kwala/ext/prefuse.jar +0 -0
- data/lib/kwala/extensions.rb +36 -0
- data/lib/kwala/lib/code_analyzer.rb +678 -0
- data/lib/kwala/lib/cycle_detector.rb +793 -0
- data/lib/kwala/lib/strange_requires_detector.rb +145 -0
- data/lib/kwala/notification.rb +45 -0
- data/lib/kwala/notifications/email.rb +54 -0
- data/lib/kwala/notifications/rss.rb +151 -0
- data/lib/kwala/project_builder_utils.rb +178 -0
- data/lib/kwala/templates/build_template.html +33 -0
- data/lib/kwala/templates/code_change_summary.html +27 -0
- data/lib/kwala/templates/code_coverage_detailed.html +25 -0
- data/lib/kwala/templates/code_coverage_summary.html +30 -0
- data/lib/kwala/templates/code_duplication_detailed.html +45 -0
- data/lib/kwala/templates/code_duplication_summary.html +9 -0
- data/lib/kwala/templates/code_formatting_detailed.html +27 -0
- data/lib/kwala/templates/code_formatting_summary.html +11 -0
- data/lib/kwala/templates/cycle_detection_detailed.html +20 -0
- data/lib/kwala/templates/cycle_detection_summary.html +7 -0
- data/lib/kwala/templates/cyclomatic_complexity_summary.html +27 -0
- data/lib/kwala/templates/executable_files_check_detailed.html +31 -0
- data/lib/kwala/templates/executable_files_check_summary.html +20 -0
- data/lib/kwala/templates/flagged_comments_detailed.html +22 -0
- data/lib/kwala/templates/flagged_comments_summary.html +10 -0
- data/lib/kwala/templates/loc_count_detailed.html +24 -0
- data/lib/kwala/templates/loc_count_summary.html +14 -0
- data/lib/kwala/templates/mdd/1.png +0 -0
- data/lib/kwala/templates/mdd/2.png +0 -0
- data/lib/kwala/templates/mdd/3.png +0 -0
- data/lib/kwala/templates/mdd/4.png +0 -0
- data/lib/kwala/templates/mdd/5.png +0 -0
- data/lib/kwala/templates/outside_links_summary.html +4 -0
- data/lib/kwala/templates/strange_requires_detailed.html +23 -0
- data/lib/kwala/templates/strange_requires_summary.html +13 -0
- data/lib/kwala/templates/style.css +95 -0
- data/lib/kwala/templates/syntax_check_detailed.html +21 -0
- data/lib/kwala/templates/syntax_check_summary.html +7 -0
- data/lib/kwala/templates/unit_test_detailed.html +66 -0
- data/lib/kwala/templates/unit_test_summary.html +70 -0
- data/test/tc_build_action.rb +32 -0
- data/test/tc_templates.rb +24 -0
- metadata +118 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
class BuildAction
|
2
|
+
extend InheritanceTracker
|
3
|
+
|
4
|
+
def build_action(context)
|
5
|
+
raise "Must be implemented by subclass."
|
6
|
+
end
|
7
|
+
|
8
|
+
def summary_display(context)
|
9
|
+
raise "Must be implemented by subclass."
|
10
|
+
end
|
11
|
+
|
12
|
+
def detailed_display(context)
|
13
|
+
[nil, nil]
|
14
|
+
end
|
15
|
+
|
16
|
+
# a number from 1 - 10 that represents some type of rating that will help
|
17
|
+
# determine if the koala's expression.
|
18
|
+
# For now if nil, then not scorable. This might be better as an additional method
|
19
|
+
# called scoreable?
|
20
|
+
# Default is unscoreable
|
21
|
+
def score
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.command_line_action_name
|
26
|
+
self.name.gsub(/BuildAction|Action/, '').kwala_underscore
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.summary_template_file
|
30
|
+
"#{TEMPLATE_DIR}/#{command_line_action_name}_summary.html"
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.detailed_template_file
|
34
|
+
"#{TEMPLATE_DIR}/#{command_line_action_name}_detailed.html"
|
35
|
+
end
|
36
|
+
|
37
|
+
def BuildAction.create_action_from_command_line_name(name)
|
38
|
+
imp = BuildAction.get_implementors.find do |imp|
|
39
|
+
imp.command_line_action_name == name
|
40
|
+
end
|
41
|
+
|
42
|
+
if imp
|
43
|
+
imp.new
|
44
|
+
else
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def BuildAction.command_line_action_names
|
50
|
+
BuildAction.get_implementors.map do |imp|
|
51
|
+
imp.command_line_action_name
|
52
|
+
end.sort
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def time_out_readlines(io, timeout_period = 300)
|
59
|
+
res = nil
|
60
|
+
begin
|
61
|
+
timeout(timeout_period) do
|
62
|
+
res = io.readlines
|
63
|
+
end
|
64
|
+
rescue TimeoutError => err
|
65
|
+
res = ["IO readlines timed out."]
|
66
|
+
end
|
67
|
+
res
|
68
|
+
end
|
69
|
+
|
70
|
+
def summary_expand(template, context)
|
71
|
+
res = ProjectBuilderUtils.expand_template(template, context.amrita_data)
|
72
|
+
Amrita::SanitizedString.new(res)
|
73
|
+
end
|
74
|
+
|
75
|
+
# These are bad param names, need to make them better
|
76
|
+
# Extremely naive function to put the errors to some scale around 10.
|
77
|
+
def score_scaling(errors, bounds)
|
78
|
+
10 - ((errors.to_f / bounds.to_f) * 10).ceil
|
79
|
+
end
|
80
|
+
|
81
|
+
def notify(result, data = nil)
|
82
|
+
Notification.notify(self.class.command_line_action_name, result, data)
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
class KwalaAbortBuildException < StandardError
|
88
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class BuildContext
|
2
|
+
attr_accessor :project_directory, :ruby_files, :test_files
|
3
|
+
attr_accessor :amrita_data, :ri_dir_1, :ri_dir_2, :pre_requires, :require
|
4
|
+
attr_accessor :output_directory
|
5
|
+
attr_accessor :vcs_num
|
6
|
+
attr_reader :project_name
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@project_directory = @project_name = @ri_dir_1 = @ri_dir_2 =
|
10
|
+
@output_directory = @require = nil
|
11
|
+
@ruby_files = Array.new
|
12
|
+
@test_files = Array.new
|
13
|
+
@pre_requires = nil
|
14
|
+
self.initialize_build_data
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def initialize_build_data()
|
19
|
+
@amrita_data = Hash.new
|
20
|
+
@amrita_data[:project_name] = self.project_name
|
21
|
+
@amrita_data[:build_date] = Time.now
|
22
|
+
end
|
23
|
+
|
24
|
+
def project_name=(project_name)
|
25
|
+
@project_name = project_name
|
26
|
+
@amrita_data[:project_name] = @project_name
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_complete
|
30
|
+
@amrita_data[:complete_time] = Time.now
|
31
|
+
@amrita_data[:total_build_time] = ( (@amrita_data[:complete_time] - @amrita_data[:build_date]) / 60.0 )
|
32
|
+
end
|
33
|
+
|
34
|
+
def vcs_info
|
35
|
+
if @vcs_info.nil?
|
36
|
+
# For now svn
|
37
|
+
cur = Dir.pwd
|
38
|
+
begin
|
39
|
+
Dir.chdir(self.project_directory)
|
40
|
+
@vcs_info = `svn log -r #{self.vcs_num} --verbose`
|
41
|
+
ensure
|
42
|
+
Dir.chdir(cur)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
@vcs_info
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
#! /usr/bin/ruby1.8
|
2
|
+
|
3
|
+
# Copyright (c) 2006, Ubiquitous Business Technology (http://ubit.com)
|
4
|
+
# All rights reserved.
|
5
|
+
#
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
7
|
+
# modification, are permitted provided that the following conditions are
|
8
|
+
# met:
|
9
|
+
#
|
10
|
+
#
|
11
|
+
# * Redistributions of source code must retain the above copyright
|
12
|
+
# notice, this list of conditions and the following disclaimer.
|
13
|
+
#
|
14
|
+
# * Redistributions in binary form must reproduce the above
|
15
|
+
# copyright notice, this list of conditions and the following
|
16
|
+
# disclaimer in the documentation and/or other materials provided
|
17
|
+
# with the distribution.
|
18
|
+
#
|
19
|
+
# * Neither the name of Ubit nor the names of its
|
20
|
+
# contributors may be used to endorse or promote products derived
|
21
|
+
# from this software without specific prior written permission.
|
22
|
+
#
|
23
|
+
#
|
24
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
25
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
26
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
27
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
28
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
29
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
30
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
31
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
32
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
33
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
34
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
35
|
+
#
|
36
|
+
# == Author
|
37
|
+
# Zev Blut (zb@ubit.com)
|
38
|
+
|
39
|
+
puts "Starting #{Time.now}"
|
40
|
+
|
41
|
+
require 'getoptlong'
|
42
|
+
require 'kwala'
|
43
|
+
|
44
|
+
def usage
|
45
|
+
puts "Usage: #{$0} [-d -p -a -y] -o"
|
46
|
+
puts " --project_name, -p (project name): The name of the project"
|
47
|
+
puts " --directory, -d (project base directory): The base directory of the project."
|
48
|
+
puts " if not set then current directory is assumed."
|
49
|
+
puts " --action, -a (action name): The name of an action to execute."
|
50
|
+
puts " if no action is set then all actions are assumed."
|
51
|
+
puts " --yaml, -y (yaml debug file name): The name of a file to save yaml debug information to."
|
52
|
+
puts " --output_dir, -o (output directory): The directory to output results to."
|
53
|
+
puts " --require,-r (file to require): A file to require for use when testing unit tests."
|
54
|
+
puts " --ri_cmp_1,-1 (old ri directory): The old ri output directory to compare against."
|
55
|
+
puts " --ri_cmp_2,-2 (new ri directory): The new ri output directory to compare against."
|
56
|
+
puts " --list_actions, -l: Lists the action names available."
|
57
|
+
puts " --notifications, -n: Activates notifications for action results."
|
58
|
+
puts " --vcs_num, -v: A string with the vcs version numbers to check"
|
59
|
+
exit 1
|
60
|
+
end
|
61
|
+
|
62
|
+
def list_actions
|
63
|
+
puts "Available actions are: "
|
64
|
+
BuildAction.command_line_action_names.each do |name|
|
65
|
+
puts " #{ name }"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
ProjectBuilderUtils.load_actions
|
70
|
+
|
71
|
+
actions = Array.new
|
72
|
+
context = BuildContext.new
|
73
|
+
|
74
|
+
context.project_directory = Dir.pwd
|
75
|
+
context.project_name = ""
|
76
|
+
debug_results = nil
|
77
|
+
begin
|
78
|
+
options = GetoptLong.new(
|
79
|
+
["-p", "--project_name", GetoptLong::REQUIRED_ARGUMENT],
|
80
|
+
["-d", "--dir", GetoptLong::REQUIRED_ARGUMENT],
|
81
|
+
["-a", "--action", GetoptLong::REQUIRED_ARGUMENT],
|
82
|
+
["-y", "--yaml", GetoptLong::REQUIRED_ARGUMENT],
|
83
|
+
["-r", "--require", GetoptLong::REQUIRED_ARGUMENT],
|
84
|
+
["-1", "--ri_cmp_1", GetoptLong::REQUIRED_ARGUMENT],
|
85
|
+
["-2", "--ri_cmp_2", GetoptLong::REQUIRED_ARGUMENT],
|
86
|
+
["-e", "--extensions", GetoptLong::REQUIRED_ARGUMENT],
|
87
|
+
["-o", "--output_dir", GetoptLong::REQUIRED_ARGUMENT],
|
88
|
+
["-l", "--list_actions", GetoptLong::NO_ARGUMENT],
|
89
|
+
["-n", "--notifications", GetoptLong::NO_ARGUMENT],
|
90
|
+
["-v", "--vcs_num", GetoptLong::REQUIRED_ARGUMENT] )
|
91
|
+
|
92
|
+
options.each do |opt, arg|
|
93
|
+
case opt
|
94
|
+
when "-p"
|
95
|
+
context.project_name = arg
|
96
|
+
when "-a"
|
97
|
+
actions << arg
|
98
|
+
when "-d"
|
99
|
+
context.project_directory = arg
|
100
|
+
when "-y"
|
101
|
+
debug_results = arg
|
102
|
+
when "-o"
|
103
|
+
context.output_directory = arg
|
104
|
+
when "-r"
|
105
|
+
context.pre_requires = arg
|
106
|
+
when "-1"
|
107
|
+
context.ri_dir_1 = ProjectBuilderUtils.clean_trailing_slash(arg)
|
108
|
+
when "-2"
|
109
|
+
context.ri_dir_2 = ProjectBuilderUtils.clean_trailing_slash(arg)
|
110
|
+
when "-e"
|
111
|
+
require arg
|
112
|
+
# required file should define get extensions
|
113
|
+
extensions = get_extensions
|
114
|
+
when "-l"
|
115
|
+
list_actions
|
116
|
+
exit 0
|
117
|
+
when "-n"
|
118
|
+
Notification.context = context
|
119
|
+
Notification.notifications
|
120
|
+
Notification.add_notification(EmailNotification.new)
|
121
|
+
Notification.add_notification(JabberNotification.new)
|
122
|
+
when "-v"
|
123
|
+
context.vcs_num = arg
|
124
|
+
else
|
125
|
+
STDERR.puts "Unknown argument #{opt}"
|
126
|
+
usage
|
127
|
+
end
|
128
|
+
end
|
129
|
+
rescue => err
|
130
|
+
STDERR.puts "Error: #{err}", err.backtrace
|
131
|
+
usage
|
132
|
+
end
|
133
|
+
|
134
|
+
usage if context.output_directory.nil?
|
135
|
+
|
136
|
+
# Set default actions if not passed
|
137
|
+
if actions.empty?
|
138
|
+
actions = %w(loc_count syntax_check unit_test flagged_comments)
|
139
|
+
end
|
140
|
+
|
141
|
+
if actions.include?("code_change")
|
142
|
+
usage if (context.ri_dir_1 && !context.ri_dir_2) || (!context.ri_dir_1 && context.ri_dir_2)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Set up necessary info before actions
|
146
|
+
|
147
|
+
ruby_files, test_files = ProjectBuilderUtils.get_ruby_and_unit_test_files(context.project_directory)
|
148
|
+
context.ruby_files = ruby_files
|
149
|
+
context.test_files = test_files
|
150
|
+
|
151
|
+
action_instances = Array.new
|
152
|
+
actions.each do |action|
|
153
|
+
action_obj = BuildAction.create_action_from_command_line_name(action)
|
154
|
+
if action_obj
|
155
|
+
action_instances << action_obj
|
156
|
+
else
|
157
|
+
STDERR.puts "Unknown action : #{action}"
|
158
|
+
list_actions
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Run the build stage
|
163
|
+
action_instances = ProjectBuilderUtils.build_actions(action_instances, context)
|
164
|
+
|
165
|
+
# Run the summary stage
|
166
|
+
summaries = ProjectBuilderUtils.build_summaries(action_instances, context)
|
167
|
+
|
168
|
+
if debug_results
|
169
|
+
#Check the syncro of templates
|
170
|
+
File.open(debug_results, "w") do |f|
|
171
|
+
f.puts context.amrita_data.to_yaml
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
ProjectBuilderUtils.make_detailed_reports(context, action_instances)
|
176
|
+
|
177
|
+
ProjectBuilderUtils.make_summary_report(context, summaries)
|
178
|
+
|
179
|
+
|
180
|
+
# Copy the stylesheet to the output directory
|
181
|
+
FileUtils.cp(TEMPLATE_DIR + "style.css", context.output_directory)
|
182
|
+
FileUtils.cp_r(TEMPLATE_DIR + "mdd", context.output_directory)
|
183
|
+
|
184
|
+
puts "Done #{Time.now}"
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Provides a method that gives instances of all of the includer's subclasses.
|
2
|
+
module InheritanceTracker
|
3
|
+
|
4
|
+
# Returns all subclasses of calling Class.
|
5
|
+
def get_implementors
|
6
|
+
imps = []
|
7
|
+
|
8
|
+
ObjectSpace.each_object(Class) do |klass|
|
9
|
+
if klass.ancestors.include?(self)
|
10
|
+
imps << klass
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# remove self from the list
|
15
|
+
imps - [self]
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class DeepCopy
|
21
|
+
extend Marshal
|
22
|
+
|
23
|
+
def DeepCopy.deep_copy(obj)
|
24
|
+
load(dump(obj))
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
class String
|
30
|
+
|
31
|
+
# Very Simple underscore implementation.
|
32
|
+
def kwala_underscore
|
33
|
+
self.gsub(/([A-Z])/, '_\1').downcase.gsub(/^_/, '')
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,678 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
# Copyright (c) 2006, Ubiquitous Business Technology (http://ubit.com)
|
4
|
+
# All rights reserved.
|
5
|
+
#
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
7
|
+
# modification, are permitted provided that the following conditions are
|
8
|
+
# met:
|
9
|
+
#
|
10
|
+
#
|
11
|
+
# * Redistributions of source code must retain the above copyright
|
12
|
+
# notice, this list of conditions and the following disclaimer.
|
13
|
+
#
|
14
|
+
# * Redistributions in binary form must reproduce the above
|
15
|
+
# copyright notice, this list of conditions and the following
|
16
|
+
# disclaimer in the documentation and/or other materials provided
|
17
|
+
# with the distribution.
|
18
|
+
#
|
19
|
+
# * Neither the name of Ubit nor the names of its
|
20
|
+
# contributors may be used to endorse or promote products derived
|
21
|
+
# from this software without specific prior written permission.
|
22
|
+
#
|
23
|
+
#
|
24
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
25
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
26
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
27
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
28
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
29
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
30
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
31
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
32
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
33
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
34
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
35
|
+
#
|
36
|
+
# == Author
|
37
|
+
# Zev Blut (zb@ubit.com)
|
38
|
+
|
39
|
+
require 'irb/ruby-lex'
|
40
|
+
require 'pp'
|
41
|
+
|
42
|
+
# Add indent to token
|
43
|
+
module RubyToken
|
44
|
+
class Token
|
45
|
+
attr_accessor :indent
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class FileAnalyzer
|
50
|
+
include RubyToken
|
51
|
+
|
52
|
+
attr_reader :file , :requires , :classes , :modules , :constants ,
|
53
|
+
:methods , :g_vars , :i_vars , :c_vars ,
|
54
|
+
:symbols , :blocks , :yields , :raises , :rescues , :cases , :aliases ,
|
55
|
+
:undefs , :ensures , :evals , :big_begins , :big_ends , :exits , :defines ,
|
56
|
+
:retries , :redoes , :nexts , :breaks , :ensures , :assignments ,
|
57
|
+
:is_a_kind_ofs , :method_missing , :static_methods
|
58
|
+
|
59
|
+
def initialize(file)
|
60
|
+
@file = file
|
61
|
+
@requires = []
|
62
|
+
@classes = []
|
63
|
+
@modules = []
|
64
|
+
@constants = []
|
65
|
+
@methods = []
|
66
|
+
@g_vars = []
|
67
|
+
@i_vars = []
|
68
|
+
@c_vars = []
|
69
|
+
@symbols = []
|
70
|
+
@blocks = 0
|
71
|
+
@yields = 0
|
72
|
+
@raises = 0
|
73
|
+
@rescues = 0
|
74
|
+
@cases = 0
|
75
|
+
@aliases = []
|
76
|
+
@undefs = []
|
77
|
+
@ensures = 0
|
78
|
+
@evals = 0
|
79
|
+
@big_begins = 0
|
80
|
+
@big_ends = 0
|
81
|
+
@exits = 0
|
82
|
+
@defines = 0
|
83
|
+
@retries = 0
|
84
|
+
@redoes = 0
|
85
|
+
@nexts = 0
|
86
|
+
@breaks = 0
|
87
|
+
@ensures = 0
|
88
|
+
@assignments = 0
|
89
|
+
@is_a_kind_ofs = 0
|
90
|
+
@method_missing = 0
|
91
|
+
@static_methods = []
|
92
|
+
@tl_ref = Hash.new
|
93
|
+
end
|
94
|
+
|
95
|
+
def analyze
|
96
|
+
#puts "Lexing File: #{ @file }"
|
97
|
+
|
98
|
+
lexer = RubyLex.new
|
99
|
+
lexer.exception_on_syntax_error = false
|
100
|
+
lexer.set_input(File.new(@file,"r"))
|
101
|
+
|
102
|
+
tokens = Array.new
|
103
|
+
while (tok = lexer.token)
|
104
|
+
tok.indent = lexer.indent
|
105
|
+
tokens<< tok
|
106
|
+
@tl_ref[tok.object_id] = lexer.line_no
|
107
|
+
end
|
108
|
+
|
109
|
+
i = 0
|
110
|
+
while i < tokens.size
|
111
|
+
ni = check_token(tokens[i], tokens[i..-1])
|
112
|
+
i += ni
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
# types, rem = BaseType.create_types_from_tokens(tokens)
|
117
|
+
|
118
|
+
|
119
|
+
# puts "TYPES ----------------"
|
120
|
+
# pp types
|
121
|
+
# exit 1
|
122
|
+
|
123
|
+
self
|
124
|
+
rescue RubyLex::SyntaxError => synerr
|
125
|
+
$stderr.puts synerr
|
126
|
+
ensure
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
def summarize
|
131
|
+
puts "-- Summary for #{ @file } --",""
|
132
|
+
puts "Requires".ljust(15) + ": " + @requires.size.to_s
|
133
|
+
puts "Modules".ljust(15) + ": " + @modules.size.to_s
|
134
|
+
puts "Classes".ljust(15) + ": " + @classes.size.to_s
|
135
|
+
puts "Constants".ljust(15) + ": " + @constants.size.to_s
|
136
|
+
puts "Methods".ljust(15) + ": " + @methods.size.to_s
|
137
|
+
puts "Static Methods".ljust(15) + ": " + @static_methods.size.to_s
|
138
|
+
puts "G Vars".ljust(15) + ": " + @g_vars.size.to_s
|
139
|
+
puts "C Vars".ljust(15) + ": " + @c_vars.size.to_s
|
140
|
+
puts "I Vars".ljust(15) + ": " + @i_vars.size.to_s
|
141
|
+
puts "Symbols".ljust(15) + ": " + @symbols.size.to_s
|
142
|
+
puts "Blocks".ljust(15) + ": " + @blocks.to_s
|
143
|
+
puts "Yields".ljust(15) + ": " + @yields.to_s
|
144
|
+
puts "Raises".ljust(15) + ": " + @raises.to_s
|
145
|
+
puts "Rescues".ljust(15) + ": " + @rescues.to_s
|
146
|
+
puts "Retries".ljust(15) + ": " + @retries.to_s
|
147
|
+
puts "Redoes".ljust(15) + ": " + @redoes.to_s
|
148
|
+
puts "Ensures".ljust(15) + ": " + @ensures.to_s
|
149
|
+
puts "Undefs".ljust(15) + ": " + @undefs.size.to_s
|
150
|
+
puts "Evals".ljust(15) + ": " + @evals.to_s
|
151
|
+
puts "Aliases".ljust(15) + ": " + @aliases.size.to_s
|
152
|
+
puts "BEGINs".ljust(15) + ": " + @big_begins.to_s
|
153
|
+
puts "ENDs".ljust(15) + ": " + @big_ends.to_s
|
154
|
+
puts "Exits".ljust(15) + ": " + @exits.to_s
|
155
|
+
puts "Defined?".ljust(15) + ": " + @defines.to_s
|
156
|
+
puts "Is_a? ".ljust(15) + ": " + @is_a_kind_ofs.to_s
|
157
|
+
puts "M missing".ljust(15) + ": " + @method_missing.to_s
|
158
|
+
puts "Nexts".ljust(15) + ": " + @nexts.to_s
|
159
|
+
puts "Breaks".ljust(15) + ": " + @breaks.to_s
|
160
|
+
puts "Cases".ljust(15) + ": " + @cases.to_s
|
161
|
+
puts "Assignments".ljust(15) + ": " + @assignments.to_s
|
162
|
+
puts "","",""
|
163
|
+
|
164
|
+
if @constants.size > 0
|
165
|
+
puts "","",""
|
166
|
+
puts "Constants ->", @constants.sort.uniq
|
167
|
+
end
|
168
|
+
|
169
|
+
if @static_methods.size > 0
|
170
|
+
puts "","",""
|
171
|
+
puts "Static methods ->", @static_methods.sort.uniq
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
puts "","----------------------------"
|
176
|
+
end
|
177
|
+
|
178
|
+
def check_token(tok, stack)
|
179
|
+
i = 1
|
180
|
+
case tok
|
181
|
+
when TkCLASS
|
182
|
+
name, i = find_identifier_name(stack)
|
183
|
+
@classes<< name
|
184
|
+
when TkMODULE
|
185
|
+
name, i = find_identifier_name(stack)
|
186
|
+
@modules<< name
|
187
|
+
when TkCONSTANT
|
188
|
+
#@contants<< tok.name
|
189
|
+
i = find_static_method_call(tok, stack)
|
190
|
+
when TkDEF
|
191
|
+
name, i = find_identifier_name(stack)
|
192
|
+
@methods<< name
|
193
|
+
when TkIDENTIFIER, TkFID
|
194
|
+
#@
|
195
|
+
case tok.name
|
196
|
+
when "require"
|
197
|
+
@requires<< nil
|
198
|
+
when "raise"
|
199
|
+
@raises += 1
|
200
|
+
when "eval", "instance_eval", "class_eval", "module_eval"
|
201
|
+
@evals += 1
|
202
|
+
when "is_a?", "kind_of?"
|
203
|
+
@is_a_kind_ofs += 1
|
204
|
+
when "method_missing"
|
205
|
+
@method_missing += 1
|
206
|
+
when /^exit!?$/
|
207
|
+
@exits += 1
|
208
|
+
end
|
209
|
+
|
210
|
+
when TkSYMBEG
|
211
|
+
name, i = find_identifier_name(stack)
|
212
|
+
@symbols<< name
|
213
|
+
when TkCVAR
|
214
|
+
@c_vars<< tok.name
|
215
|
+
when TkIVAR
|
216
|
+
@i_vars<< tok.name
|
217
|
+
when TkGVAR
|
218
|
+
@g_vars << tok.name
|
219
|
+
when TkDO,TkfLBRACE
|
220
|
+
# do
|
221
|
+
@blocks += 1
|
222
|
+
when TkYIELD
|
223
|
+
@yields += 1
|
224
|
+
when TkEND
|
225
|
+
when TkRESCUE
|
226
|
+
@rescues += 1
|
227
|
+
when TklBEGIN
|
228
|
+
@big_begins += 1
|
229
|
+
when TklEND
|
230
|
+
@big_ends += 1
|
231
|
+
when TkDEFINED
|
232
|
+
@defines += 1
|
233
|
+
when TkRETRY
|
234
|
+
@retries += 1
|
235
|
+
when TkREDO
|
236
|
+
@redoes += 1
|
237
|
+
when TkNEXT
|
238
|
+
@nexts += 1
|
239
|
+
when TkBREAK
|
240
|
+
@breaks += 1
|
241
|
+
when TkENSURE
|
242
|
+
@ensures += 1
|
243
|
+
when TkUNDEF
|
244
|
+
@undefs << nil
|
245
|
+
when TkCASE
|
246
|
+
@cases += 1
|
247
|
+
when TkALIAS
|
248
|
+
name, i = find_identifier_name(stack)
|
249
|
+
@aliases << name
|
250
|
+
when TkASSIGN, TkOPASGN
|
251
|
+
@assignments += 1
|
252
|
+
else
|
253
|
+
|
254
|
+
end
|
255
|
+
i
|
256
|
+
end
|
257
|
+
|
258
|
+
def find_identifier_name(stream, check_types = [TkIDENTIFIER, TkFID, TkCONSTANT])
|
259
|
+
i = 1
|
260
|
+
name = ""
|
261
|
+
found = false
|
262
|
+
|
263
|
+
while !found && stream[i]
|
264
|
+
tok = stream[i]
|
265
|
+
if check_types.include?(tok.class)
|
266
|
+
found = true
|
267
|
+
name = tok.name
|
268
|
+
else
|
269
|
+
i += 1
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
[name, i]
|
274
|
+
end
|
275
|
+
|
276
|
+
def find_static_method_call(tok, stream)
|
277
|
+
sname = tok.name.dup
|
278
|
+
searching = true
|
279
|
+
i = 1
|
280
|
+
while searching
|
281
|
+
val = stream[i]
|
282
|
+
case val
|
283
|
+
when TkDOT
|
284
|
+
sname<< "."
|
285
|
+
when TkCOLON2
|
286
|
+
sname<< "::"
|
287
|
+
when TkCONSTANT, TkIDENTIFIER, TkFID
|
288
|
+
sname << val.name
|
289
|
+
when TkNL, TkSPACE
|
290
|
+
searching = false
|
291
|
+
when TkLPAREN
|
292
|
+
sname<< "()"
|
293
|
+
searching = false
|
294
|
+
when TkfLBRACK
|
295
|
+
sname<< "[]"
|
296
|
+
searching = false
|
297
|
+
when TkfLBRACE
|
298
|
+
searching = false
|
299
|
+
when TkRPAREN, TkRBRACK, TkRBRACE, TkDOT2, TkDOT3, TkBITOR, TkBITAND,
|
300
|
+
TkCOMMA, TkSEMICOLON, TkPLUS, TkMINUS, TkMULT, TkDIV
|
301
|
+
# close of a method call
|
302
|
+
searching = false
|
303
|
+
when TkASSIGN, TkMOD
|
304
|
+
# could be class = ? most likely a constant
|
305
|
+
searching = false
|
306
|
+
when TkDSTRING, TkSTRING
|
307
|
+
# ignore the constant as it is wrapped in a string
|
308
|
+
searching = false
|
309
|
+
else
|
310
|
+
puts "NEW CASE #{ val.class } for #{self.file}"
|
311
|
+
puts "Line: #{ @tl_ref[val.object_id] }"
|
312
|
+
searching = false
|
313
|
+
end
|
314
|
+
i += 1
|
315
|
+
end
|
316
|
+
if tok.name != sname
|
317
|
+
# Make sure we don't get things like Array[] or Array()
|
318
|
+
if /:/ =~ sname && /^[A-Z]/ =~ sname.split(".").last.split(":").last
|
319
|
+
@constants << sname
|
320
|
+
else
|
321
|
+
@static_methods<< sname
|
322
|
+
end
|
323
|
+
else
|
324
|
+
@constants << sname
|
325
|
+
#puts "JUST A CONST :#{ sname } ----"
|
326
|
+
#puts "Line: #{ @tl_ref[tok.object_id] }"
|
327
|
+
end
|
328
|
+
i
|
329
|
+
end
|
330
|
+
|
331
|
+
def group_methods(tokens)
|
332
|
+
# meth_groups = []
|
333
|
+
#
|
334
|
+
# cur_group_toks = []
|
335
|
+
# indent = 0
|
336
|
+
# indef = false
|
337
|
+
#
|
338
|
+
# tokens.each do |tok|
|
339
|
+
# cur_group_toks<< tok
|
340
|
+
#
|
341
|
+
# if tok.class == TkDEF
|
342
|
+
# indent = tok.indent
|
343
|
+
# indef = true
|
344
|
+
# cur_group_toks = [ tok ]
|
345
|
+
# elsif tok.class == TkEND
|
346
|
+
# if indef && (tok.indent == (indent - 1))
|
347
|
+
# indef = false
|
348
|
+
# indent = tok.indent
|
349
|
+
#
|
350
|
+
# meth_groups<< cur_group_toks
|
351
|
+
# cur_group_toks = []
|
352
|
+
# end
|
353
|
+
# end
|
354
|
+
# end
|
355
|
+
#
|
356
|
+
# puts "Meth group #{ meth_groups.size }"
|
357
|
+
# pp meth_groups
|
358
|
+
# exit
|
359
|
+
#
|
360
|
+
groups, idx = group_tokens(tokens, [TkDEF])
|
361
|
+
puts "Meth groups cnt #{ groups.size }"
|
362
|
+
pp groups
|
363
|
+
exit
|
364
|
+
end
|
365
|
+
|
366
|
+
|
367
|
+
|
368
|
+
end
|
369
|
+
|
370
|
+
|
371
|
+
|
372
|
+
|
373
|
+
class FileAnalyzerGroup
|
374
|
+
def initialize(files)
|
375
|
+
@fa_files = []
|
376
|
+
files.each do |file|
|
377
|
+
@fa_files<< FileAnalyzer.new(file).analyze
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def summarize
|
382
|
+
fst = @fa_files.first
|
383
|
+
vals = fst.instance_variables.map { |iv| iv[1..-1] }
|
384
|
+
# remove the instance variables we set that are not related to data tracking.
|
385
|
+
vals = vals - ["file", "tl_ref"]
|
386
|
+
t_v = vals.map do |v|
|
387
|
+
res = fst.send(v)
|
388
|
+
|
389
|
+
if res.is_a?(Array)
|
390
|
+
p = Proc.new { |v| v.size }
|
391
|
+
else
|
392
|
+
p = Proc.new { |v| v.to_i }
|
393
|
+
end
|
394
|
+
[v, p]
|
395
|
+
end
|
396
|
+
|
397
|
+
res = Hash.new { |h,k| h[k] = [] }
|
398
|
+
@fa_files.each do |f|
|
399
|
+
t_v.each do |method, calc|
|
400
|
+
res[method]<< calc.call( f.send(method) )
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
|
405
|
+
base_sum = Hash.new(0)
|
406
|
+
|
407
|
+
res.sort.each do |key, val|
|
408
|
+
r = val.inject(0) { |t, v| t + v }
|
409
|
+
base_sum[key] = r
|
410
|
+
end
|
411
|
+
|
412
|
+
print_summary_header(base_sum)
|
413
|
+
|
414
|
+
extra_summary( [ ["static_methods", "static methods"],
|
415
|
+
["g_vars", "global variables"],
|
416
|
+
["c_vars", "class variables"],
|
417
|
+
["i_vars", "instance variables"],
|
418
|
+
["constants", "constants"],
|
419
|
+
["methods", "methods"],
|
420
|
+
["classes", "classes"],
|
421
|
+
["modules", "modules"],
|
422
|
+
["symbols", "symbols"],
|
423
|
+
["aliases", "aliases"],
|
424
|
+
["undefs", "undefs"]
|
425
|
+
] )
|
426
|
+
|
427
|
+
end
|
428
|
+
|
429
|
+
def print_summary_header(summary)
|
430
|
+
puts "Summary"
|
431
|
+
summary.sort.each do |key, res|
|
432
|
+
puts "#{ key.ljust(15) } : #{res}"
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
def extra_summary(methods)
|
437
|
+
ttl_files = @fa_files.size
|
438
|
+
|
439
|
+
methods.each do |mcall, mname|
|
440
|
+
cnt = @fa_files.inject(0) { |cnt, f| cnt += (f.send(mcall).size > 0 ? 1 : 0) }
|
441
|
+
if cnt == 0
|
442
|
+
next
|
443
|
+
end
|
444
|
+
msg = "#{ cnt } out of #{ ttl_files }" +
|
445
|
+
" (#{sprintf("%.2f",(cnt.to_f / ttl_files) * 100)} %) use #{ mname}"
|
446
|
+
|
447
|
+
|
448
|
+
uniqs = Hash.new { |h,k| h[k] = [] }
|
449
|
+
@fa_files.each do |f|
|
450
|
+
f.send(mcall).uniq.each do |sm|
|
451
|
+
uniqs[sm] << f.file
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
print_extra_summary(mname, msg, uniqs)
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
def print_extra_summary(name, msg, uniqs)
|
460
|
+
puts "" , "---" + msg + "---"
|
461
|
+
puts "#{name} used by files:", ""
|
462
|
+
uniqs.sort.each do |k, m|
|
463
|
+
puts k.to_s.ljust(30) + " -> ", " " + m.join("\n "), ""
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
end
|
468
|
+
|
469
|
+
class BaseType
|
470
|
+
include RubyToken
|
471
|
+
|
472
|
+
attr_accessor :name, :start_line, :end_line, :children, :tokens
|
473
|
+
|
474
|
+
def initialize(tokens = Array.new)
|
475
|
+
@children = Array.new
|
476
|
+
|
477
|
+
# These may be computed by tokens?
|
478
|
+
@start_line = 0
|
479
|
+
@end_line = 0
|
480
|
+
@tokens = tokens
|
481
|
+
@rem_tokens = []
|
482
|
+
|
483
|
+
create_child_groupings
|
484
|
+
analyse_self
|
485
|
+
end
|
486
|
+
|
487
|
+
def sub_types
|
488
|
+
@children.each { |c| yield c }
|
489
|
+
end
|
490
|
+
|
491
|
+
|
492
|
+
def self.create_types_from_tokens(tokens)
|
493
|
+
groups, idx = group_tokens(tokens, [TkCLASS, TkMODULE, TkDEF])
|
494
|
+
|
495
|
+
|
496
|
+
remainder_tokens = []
|
497
|
+
types = []
|
498
|
+
|
499
|
+
groups.each do |sub_tokens|
|
500
|
+
|
501
|
+
case sub_tokens.first
|
502
|
+
when TkCLASS
|
503
|
+
types<< ClassType.new(sub_tokens)
|
504
|
+
when TkMODULE
|
505
|
+
types<< ModuleType.new(sub_tokens)
|
506
|
+
when TkDEF
|
507
|
+
types<< DefType.new(sub_tokens)
|
508
|
+
else
|
509
|
+
remainder_tokens<< sub_tokens
|
510
|
+
end
|
511
|
+
|
512
|
+
end
|
513
|
+
|
514
|
+
[types, remainder_tokens]
|
515
|
+
end
|
516
|
+
|
517
|
+
private
|
518
|
+
def self.group_tokens(tokens, trigger, indent_target = nil, idx = 0)
|
519
|
+
groups = []
|
520
|
+
|
521
|
+
while idx < tokens.size
|
522
|
+
tok = tokens[idx]
|
523
|
+
idx += 1
|
524
|
+
|
525
|
+
if trigger.include?(tok.class)
|
526
|
+
indent = tok.indent
|
527
|
+
|
528
|
+
if indent_target.nil?
|
529
|
+
indent_target = indent - 1
|
530
|
+
end
|
531
|
+
|
532
|
+
|
533
|
+
sub_group, idx = collect_into_group(tokens, indent - 1, idx)
|
534
|
+
|
535
|
+
groups << [tok] + sub_group
|
536
|
+
end
|
537
|
+
|
538
|
+
end
|
539
|
+
|
540
|
+
[groups, idx]
|
541
|
+
end
|
542
|
+
|
543
|
+
|
544
|
+
def self.collect_into_group(tokens, indent_target, idx)
|
545
|
+
group = []
|
546
|
+
|
547
|
+
while idx < tokens.size
|
548
|
+
tok = tokens[idx]
|
549
|
+
idx += 1
|
550
|
+
|
551
|
+
group << tok
|
552
|
+
if tok.class == TkEND
|
553
|
+
if tok.indent == indent_target
|
554
|
+
return [group, idx]
|
555
|
+
elsif tok.indent < indent_target
|
556
|
+
puts "ERROR INDENT lower than TARGET"
|
557
|
+
return [group, idx]
|
558
|
+
else
|
559
|
+
# puts "NO CARE"
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
puts "ERROR FELL out while looking for END!"
|
565
|
+
return [group, idx]
|
566
|
+
end
|
567
|
+
|
568
|
+
|
569
|
+
def create_child_groupings
|
570
|
+
# ignore first so that we do not get stuck on self and last end
|
571
|
+
@children, rem = BaseType.create_types_from_tokens(@tokens[1..-2])
|
572
|
+
@rem_tokens = rem
|
573
|
+
end
|
574
|
+
|
575
|
+
def analyse_self
|
576
|
+
end
|
577
|
+
|
578
|
+
end
|
579
|
+
|
580
|
+
|
581
|
+
class ClassType < BaseType
|
582
|
+
attr_accessor :super_class, :includes
|
583
|
+
|
584
|
+
def analyse_self
|
585
|
+
|
586
|
+
|
587
|
+
@name = find_c_m_name
|
588
|
+
@super_class = find_super_class
|
589
|
+
@includes = find_includes
|
590
|
+
end
|
591
|
+
|
592
|
+
def find_c_m_name
|
593
|
+
""
|
594
|
+
end
|
595
|
+
|
596
|
+
def find_super_class
|
597
|
+
nil
|
598
|
+
end
|
599
|
+
|
600
|
+
def find_includes
|
601
|
+
[]
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
class ModuleType < BaseType
|
606
|
+
attr_accessor :includes
|
607
|
+
end
|
608
|
+
|
609
|
+
class DefType < BaseType
|
610
|
+
attr_accessor :arguments, :raises
|
611
|
+
|
612
|
+
def analyse_self
|
613
|
+
find_name
|
614
|
+
find_arguments
|
615
|
+
find_raises
|
616
|
+
end
|
617
|
+
|
618
|
+
def find_name
|
619
|
+
end
|
620
|
+
|
621
|
+
def find_arguments
|
622
|
+
end
|
623
|
+
|
624
|
+
def find_raises
|
625
|
+
end
|
626
|
+
|
627
|
+
end
|
628
|
+
|
629
|
+
if __FILE__ == $0
|
630
|
+
FileAnalyzerGroup.new(ARGV).summarize
|
631
|
+
end
|
632
|
+
|
633
|
+
__END__
|
634
|
+
|
635
|
+
# LOOK for initialize calls with calling super
|
636
|
+
#
|
637
|
+
# More things to look for
|
638
|
+
# Misspelled local/variables
|
639
|
+
# No method comments
|
640
|
+
# Unused globals and locals (module or variable)
|
641
|
+
# Unused function/method arguments (can ignore self)
|
642
|
+
|
643
|
+
#LOOK for EMPTY RESCUE blocks.
|
644
|
+
# I.e. a rescue that just catches the exception and does nothing.
|
645
|
+
|
646
|
+
# Count # of parameters per method (over 7 warn!)
|
647
|
+
# check for similar sequence of parameters in methods
|
648
|
+
# Warn if inconsistent?
|
649
|
+
# i.e. foo(a, b, c) and moo(c, a , b)
|
650
|
+
|
651
|
+
# Unused method parameters! unless it is a overriding subclass?
|
652
|
+
|
653
|
+
# Have view to show all those who subclass/include/extend a class or module (Fan in)
|
654
|
+
# Also view for normal fan out?
|
655
|
+
|
656
|
+
# Check block parameters!
|
657
|
+
|
658
|
+
# Variable bad things (smalltalk best practice p.103)
|
659
|
+
# long extent (how long its value lasts )
|
660
|
+
# wide scope (how long it can be accessed)
|
661
|
+
# large type (How many messages it can respond to)
|
662
|
+
|
663
|
+
|
664
|
+
|
665
|
+
# Method call
|
666
|
+
Line: 63 17 Token #<RubyToken::TkIDENTIFIER:0xb7d33790>: interface
|
667
|
+
Line: 63 18 Token #<RubyToken::TkDOT:0xb7d335d8>
|
668
|
+
Line: 63 31 Token #<RubyToken::TkIDENTIFIER:0xb7d33024>: billing_steps
|
669
|
+
|
670
|
+
Line: 64 13 Token #<RubyToken::TkFID:0xb7d30504>: nil?
|
671
|
+
|
672
|
+
Line: 68 2513 Token #<RubyToken::TkNIL:0xb7d27c24>: nil
|
673
|
+
|
674
|
+
|
675
|
+
Line: 78 12 Token #<RubyToken::TkCONSTANT:0xb7d1b7bc>: Registry
|
676
|
+
Line: 78 13 Token #<RubyToken::TkDOT:0xb7d1b5dc>
|
677
|
+
Line: 78 26 Token #<RubyToken::TkIDENTIFIER:0xb7d1b104>: each_behavior
|
678
|
+
|