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,222 @@
|
|
1
|
+
# $Id: code_duplication.rb 29 2008-04-02 07:32:44Z zev $
|
2
|
+
#
|
3
|
+
# This file implements the logic to check for source code duplication.
|
4
|
+
#
|
5
|
+
# Requires the Java program PMD which uses the CPD tool.
|
6
|
+
|
7
|
+
# Copyright (c) 2006, Ubiquitous Business Technology (http://ubit.com)
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use in source and binary forms, with or without
|
11
|
+
# modification, are permitted provided that the following conditions are
|
12
|
+
# met:
|
13
|
+
#
|
14
|
+
#
|
15
|
+
# * Redistributions of source code must retain the above copyright
|
16
|
+
# notice, this list of conditions and the following disclaimer.
|
17
|
+
#
|
18
|
+
# * Redistributions in binary form must reproduce the above
|
19
|
+
# copyright notice, this list of conditions and the following
|
20
|
+
# disclaimer in the documentation and/or other materials provided
|
21
|
+
# with the distribution.
|
22
|
+
#
|
23
|
+
# * Neither the name of Ubit nor the names of its
|
24
|
+
# contributors may be used to endorse or promote products derived
|
25
|
+
# from this software without specific prior written permission.
|
26
|
+
#
|
27
|
+
#
|
28
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
29
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
30
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
31
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
32
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
33
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
34
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
35
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
36
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
37
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
38
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
39
|
+
#
|
40
|
+
# == Author
|
41
|
+
# Zev Blut (zb@ubit.com)
|
42
|
+
|
43
|
+
|
44
|
+
class CodeDuplicationAction < BuildAction
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
@duplicates = nil
|
48
|
+
@file_count = 0
|
49
|
+
end
|
50
|
+
|
51
|
+
def build_action(context)
|
52
|
+
temporarily_remove_symlinks(context.project_directory) do |dir|
|
53
|
+
@duplicates = find_duplicate_code(dir)
|
54
|
+
end
|
55
|
+
# Store file count for scoring
|
56
|
+
@file_count = context.ruby_files.size + context.test_files.size
|
57
|
+
end
|
58
|
+
|
59
|
+
def summary_display(context)
|
60
|
+
template = TemplateFile.new(self.class.summary_template_file)
|
61
|
+
context.amrita_data[:duplicate_code_results] = {
|
62
|
+
:duplicate_count => @duplicates.size,
|
63
|
+
:duplicates => @duplicates
|
64
|
+
}
|
65
|
+
context.amrita_data[:code_duplication_details] =
|
66
|
+
(Amrita::e(:a, :href=>"#{context.project_name}_code_duplication.html") { "Code Duplication Details" })
|
67
|
+
summary_expand(template, context)
|
68
|
+
end
|
69
|
+
|
70
|
+
def detailed_display(context)
|
71
|
+
det_res = expand_code_duplication_template(context.amrita_data[:duplicate_code_results])
|
72
|
+
det_file = "#{context.output_directory}/#{context.project_name}_code_duplication.html"
|
73
|
+
[det_file, det_res]
|
74
|
+
end
|
75
|
+
|
76
|
+
def score
|
77
|
+
# This should be based on code size ratio, for now using file count, but
|
78
|
+
# probably lines of code is better.
|
79
|
+
if @duplicates.size > @file_count
|
80
|
+
1
|
81
|
+
else
|
82
|
+
score_scaling(@duplicates.size, @file_count)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def find_duplicate_code(dir=Dir.pwd)
|
87
|
+
require "rexml/document"
|
88
|
+
results = Array.new
|
89
|
+
Open3.popen3( cpd_command(dir) ) do |stdin, stdout, stderr|
|
90
|
+
xml = time_out_readlines(stdout)
|
91
|
+
if cpd_error?(stderr) || xml.first == "IO readlines timed out."
|
92
|
+
return results
|
93
|
+
end
|
94
|
+
doc = REXML::Document.new(xml.join("\n"))
|
95
|
+
doc.elements.each("pmd-cpd/duplication") do |dup|
|
96
|
+
results << {
|
97
|
+
:duplicate => {
|
98
|
+
:entry_num => results.size,
|
99
|
+
:lines => dup.attribute("lines").to_s,
|
100
|
+
:tokens => dup.attribute("tokens").to_s,
|
101
|
+
:code_fragement => highlight_code(dup.elements["codefragment"].cdatas.to_s),
|
102
|
+
:files => dup.get_elements("file").map { |f|
|
103
|
+
{
|
104
|
+
:path => f.attribute("path").to_s,
|
105
|
+
:line => f.attribute("line").to_s
|
106
|
+
}
|
107
|
+
},
|
108
|
+
}
|
109
|
+
}
|
110
|
+
end
|
111
|
+
end
|
112
|
+
results
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def expand_code_duplication_template(data)
|
117
|
+
template = TemplateFile.new(self.class.detailed_template_file)
|
118
|
+
data[:duplicate_refs] = data[:duplicates].map do |d|
|
119
|
+
{
|
120
|
+
:entry_num => Amrita::a(:href=>"\##{d[:duplicate][:entry_num]}") {d[:duplicate][:entry_num]}
|
121
|
+
}
|
122
|
+
end
|
123
|
+
max_size = (data[:duplicates].size - 1)
|
124
|
+
data[:duplicates].each_with_index do |d, idx|
|
125
|
+
d[:duplicate][:entry_num] = Amrita::a(:name=>"#{d[:duplicate][:entry_num]}") {d[:duplicate][:entry_num]}
|
126
|
+
if idx > 0
|
127
|
+
d[:duplicate][:prev_entry_num] = Amrita::a(:href=>"\##{idx - 1}") { "Prev" }
|
128
|
+
end
|
129
|
+
if idx < max_size
|
130
|
+
d[:duplicate][:next_entry_num] = Amrita::a(:href=>"\##{idx + 1}") { "Next" }
|
131
|
+
end
|
132
|
+
end
|
133
|
+
ProjectBuilderUtils.expand_template(template, data)
|
134
|
+
end
|
135
|
+
|
136
|
+
def highlight_code(code)
|
137
|
+
# Check if enscript is installed.
|
138
|
+
cmd = "/usr/bin/enscript --color --language=html -Eruby --output=-"
|
139
|
+
out_data = nil
|
140
|
+
Open3.popen3(cmd) do |stdin, stdout, stderr|
|
141
|
+
stdin.puts code
|
142
|
+
stdin.close
|
143
|
+
out_data = time_out_readlines(stdout).join("")
|
144
|
+
#time_out_readlines(stderr)
|
145
|
+
end
|
146
|
+
if (out_data && ( m = /(<pre>.*<\/pre>)/im.match(out_data) ) )
|
147
|
+
code = Amrita::SanitizedString.new(m[1])
|
148
|
+
end
|
149
|
+
|
150
|
+
code
|
151
|
+
end
|
152
|
+
|
153
|
+
def cpd_command(dir)
|
154
|
+
"java -Xmx256m -cp #{cpd_jar} net.sourceforge.pmd.cpd.CPD" +
|
155
|
+
" --minimum-tokens #{minimum_tokens} --files #{dir}" +
|
156
|
+
" --language ruby --format net.sourceforge.pmd.cpd.XMLRenderer"
|
157
|
+
end
|
158
|
+
|
159
|
+
def cpd_jar
|
160
|
+
EXTERNAL_DIR + "pmd.jar"
|
161
|
+
end
|
162
|
+
|
163
|
+
def minimum_tokens
|
164
|
+
50
|
165
|
+
end
|
166
|
+
|
167
|
+
def cpd_error?(stderr)
|
168
|
+
lines = stderr.readlines
|
169
|
+
if !lines.empty?
|
170
|
+
STDERR.puts "code_duplication error with cpd:", lines
|
171
|
+
true
|
172
|
+
else
|
173
|
+
false
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def temporarily_remove_symlinks(dir)
|
178
|
+
symlinks = Hash.new
|
179
|
+
|
180
|
+
Find.find(dir) do |path|
|
181
|
+
if File.symlink?(path)
|
182
|
+
lnk = File.expand_path(path)
|
183
|
+
|
184
|
+
# Need to give it relative to lnk
|
185
|
+
src = File.expand_path(File.readlink(path), File.dirname(lnk))
|
186
|
+
|
187
|
+
# Only remove symlinks that are relative to dir. i.e. inside
|
188
|
+
# project to other parts of the project.
|
189
|
+
|
190
|
+
if src.index(dir) == 0
|
191
|
+
|
192
|
+
symlinks[lnk] = src
|
193
|
+
|
194
|
+
# Store if it is a directory
|
195
|
+
dircheck = File.directory?(path)
|
196
|
+
|
197
|
+
# Remove the link
|
198
|
+
FileUtils.rm_f(path)
|
199
|
+
|
200
|
+
if dircheck
|
201
|
+
# Prune must come after removing the file, because code
|
202
|
+
# after prune will not run.
|
203
|
+
Find.prune
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
yield dir
|
211
|
+
ensure
|
212
|
+
|
213
|
+
symlinks.each do |sym, source|
|
214
|
+
begin
|
215
|
+
FileUtils.ln_s(source, sym)
|
216
|
+
rescue => err
|
217
|
+
STDERR.puts "Cannot restore link:", err
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
@@ -0,0 +1,358 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# $Id: code_formatting.rb 29 2008-04-02 07:32:44Z zev $
|
3
|
+
#
|
4
|
+
# Checks for basic formatting violations.
|
5
|
+
|
6
|
+
# Copyright (c) 2006, Ubiquitous Business Technology (http://ubit.com)
|
7
|
+
# All rights reserved.
|
8
|
+
#
|
9
|
+
# Redistribution and use in source and binary forms, with or without
|
10
|
+
# modification, are permitted provided that the following conditions are
|
11
|
+
# met:
|
12
|
+
#
|
13
|
+
#
|
14
|
+
# * Redistributions of source code must retain the above copyright
|
15
|
+
# notice, this list of conditions and the following disclaimer.
|
16
|
+
#
|
17
|
+
# * Redistributions in binary form must reproduce the above
|
18
|
+
# copyright notice, this list of conditions and the following
|
19
|
+
# disclaimer in the documentation and/or other materials provided
|
20
|
+
# with the distribution.
|
21
|
+
#
|
22
|
+
# * Neither the name of Ubit nor the names of its
|
23
|
+
# contributors may be used to endorse or promote products derived
|
24
|
+
# from this software without specific prior written permission.
|
25
|
+
#
|
26
|
+
#
|
27
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
28
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
29
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
30
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
31
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
32
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
33
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
34
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
35
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
36
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
37
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
38
|
+
#
|
39
|
+
# == Author
|
40
|
+
# Zev Blut, Michael Reinsch, Pierre Baumard
|
41
|
+
|
42
|
+
require 'stringio'
|
43
|
+
|
44
|
+
# TODO: everything except CodeFormattingAction should be extracted from this file
|
45
|
+
# - for dependency tracking & tests, cf. Coding Guidelines
|
46
|
+
# - would also fix next issue
|
47
|
+
|
48
|
+
# XXX: cheat to remove the need to require kwala
|
49
|
+
# This should be fixed at a later time.
|
50
|
+
if !defined? BuildAction
|
51
|
+
class BuildAction
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
class CodeFormattingAction < BuildAction
|
57
|
+
|
58
|
+
def initialize(receiver=AmritaViolationReceiver.new)
|
59
|
+
@receiver = receiver
|
60
|
+
@detector = ViolationDetector.new(@receiver)
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_action(context)
|
64
|
+
context.ruby_files.each do |file|
|
65
|
+
check_formatting(file)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def summary_display(context)
|
70
|
+
template = TemplateFile.new(self.class.summary_template_file)
|
71
|
+
|
72
|
+
if @receiver.empty?
|
73
|
+
context.amrita_data[:code_formatting_details] = "Code Formatting"
|
74
|
+
|
75
|
+
msg = "There are no formatting violations."
|
76
|
+
context.amrita_data[:code_formatting_results] = msg
|
77
|
+
else
|
78
|
+
|
79
|
+
context.amrita_data[:code_formatting_results] = {
|
80
|
+
:headers => ["Files"] << @receiver.summary.keys,
|
81
|
+
:values => [@receiver.files] << @receiver.summary.values,
|
82
|
+
}
|
83
|
+
|
84
|
+
url = "#{context.project_name}_code_formatting.html"
|
85
|
+
context.amrita_data[:code_formatting_details] = Amrita::a(:href => url)
|
86
|
+
end
|
87
|
+
|
88
|
+
summary_expand(template, context)
|
89
|
+
end
|
90
|
+
|
91
|
+
def detailed_display(context)
|
92
|
+
template = TemplateFile.new(self.class.detailed_template_file)
|
93
|
+
|
94
|
+
context.amrita_data[:code_formatting_details] = {
|
95
|
+
:entry => @receiver.details
|
96
|
+
}
|
97
|
+
|
98
|
+
det_res = ProjectBuilderUtils.expand_template(template,
|
99
|
+
context.amrita_data)
|
100
|
+
|
101
|
+
base_output_name = "#{ context.output_directory}/#{ context.project_name }"
|
102
|
+
det_file = base_output_name + "_code_formatting.html"
|
103
|
+
|
104
|
+
[det_file, det_res]
|
105
|
+
end
|
106
|
+
|
107
|
+
def score
|
108
|
+
return 10 if @receiver.empty?
|
109
|
+
errors = @receiver.summary.values.inject(0) {|s, v| s + v }
|
110
|
+
# For now made up mappings.
|
111
|
+
case errors
|
112
|
+
when 1..4 : 7
|
113
|
+
when 5..12 : 5
|
114
|
+
when 13..20 : 3
|
115
|
+
else
|
116
|
+
1
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Checks the formatting of a file
|
121
|
+
def check_formatting(file)
|
122
|
+
@detector.check(file)
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
class ViolationDetector
|
129
|
+
extend InheritanceTracker
|
130
|
+
|
131
|
+
attr_accessor :violation_rec
|
132
|
+
|
133
|
+
def initialize(violation_rec)
|
134
|
+
@detectors = []
|
135
|
+
@violation_rec = violation_rec
|
136
|
+
end
|
137
|
+
|
138
|
+
# If detector needs the whole file instead of the lines of the file,
|
139
|
+
# then it must overwrite this method to return false.
|
140
|
+
def uses_lines?
|
141
|
+
true
|
142
|
+
end
|
143
|
+
|
144
|
+
# Add a specific instance of a detector to use when checking for
|
145
|
+
# violations.
|
146
|
+
def add_detector(detector)
|
147
|
+
@detectors << detector
|
148
|
+
end
|
149
|
+
|
150
|
+
# Return detectors used for checking. If no detectors were
|
151
|
+
# specifically added, then an instance of each subclass of
|
152
|
+
# ViolationDetector is returned.
|
153
|
+
def violation_detectors
|
154
|
+
if @detectors.empty?
|
155
|
+
@detectors = self.class.get_implementors.map { |k| k.new(violation_rec) }
|
156
|
+
end
|
157
|
+
|
158
|
+
@detectors
|
159
|
+
end
|
160
|
+
|
161
|
+
# Checks for violations in a file.
|
162
|
+
# == Parameters
|
163
|
+
# file:: The path to a file to check.
|
164
|
+
# Note: as Katagiri proposed we could make file an IO object to make
|
165
|
+
# this even more general and powerful.
|
166
|
+
def check(file)
|
167
|
+
lines = IO.readlines(file)
|
168
|
+
|
169
|
+
line_detectors, file_detectors = violation_detectors.partition do |vd|
|
170
|
+
vd.uses_lines?
|
171
|
+
end
|
172
|
+
|
173
|
+
line_detectors.each do |detector|
|
174
|
+
detector.check_lines(file, lines)
|
175
|
+
end
|
176
|
+
|
177
|
+
file_detectors.each do |detector|
|
178
|
+
detector.check_file(file)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
#############################################################################
|
183
|
+
# Protected
|
184
|
+
#############################################################################
|
185
|
+
protected
|
186
|
+
|
187
|
+
# Overwrite this method to check against all lines in file.
|
188
|
+
# If you want to just check one line then use check_line.
|
189
|
+
# Return an array of violation, if no violations return an empty array.
|
190
|
+
def check_lines(file, lines)
|
191
|
+
viols = []
|
192
|
+
|
193
|
+
lines.each_with_index do |line, idx|
|
194
|
+
idx += 1
|
195
|
+
v = check_line(file, idx, line)
|
196
|
+
viols << v if v
|
197
|
+
end
|
198
|
+
|
199
|
+
viols
|
200
|
+
end
|
201
|
+
|
202
|
+
# Implement this method to check against a line in a file.
|
203
|
+
# Return either a violation instance or nil if no violation found.
|
204
|
+
def check_line(file, line_num, line)
|
205
|
+
end
|
206
|
+
|
207
|
+
# If uses_lines returns false implement this method to check against
|
208
|
+
# the path to a file.
|
209
|
+
def check_file(file)
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
class FileLineCountViolationDetector < ViolationDetector
|
216
|
+
|
217
|
+
MAX_LINE_COUNT = 1000
|
218
|
+
|
219
|
+
def check_lines(file, lines)
|
220
|
+
if lines.size > MAX_LINE_COUNT
|
221
|
+
@violation_rec.violation(file, "", "",
|
222
|
+
"File line count > #{MAX_LINE_COUNT}",
|
223
|
+
"Warning")
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
class TrailingWhiteSpaceViolationDetector < ViolationDetector
|
231
|
+
# cannot use \s because that adds \n which is valid.
|
232
|
+
# Just check for " " and tab for now.
|
233
|
+
TRAILING_WHITE_SPACE = /[ \t]$/
|
234
|
+
|
235
|
+
def check_line(file, line_num, line)
|
236
|
+
if TRAILING_WHITE_SPACE =~ line
|
237
|
+
@violation_rec.violation(file, line_num, line,
|
238
|
+
"Trailing White Space", "Warning")
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
class TabViolationDetector < ViolationDetector
|
246
|
+
|
247
|
+
TABS = /\t/
|
248
|
+
|
249
|
+
def check_line(file, line_num, line)
|
250
|
+
if TABS =~ line
|
251
|
+
@violation_rec.violation(file, line_num, line, "Tab", "Warning")
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
class LineLengthViolationDetector < ViolationDetector
|
259
|
+
|
260
|
+
MAX_LENGTH = 100
|
261
|
+
MAX_LENGTH_WITH_RETURN = MAX_LENGTH + 1 # with \n
|
262
|
+
|
263
|
+
def check_line(file, line_num, line)
|
264
|
+
|
265
|
+
if line.length > MAX_LENGTH_WITH_RETURN
|
266
|
+
@violation_rec.violation(file, line_num, line,
|
267
|
+
"Line length > #{MAX_LENGTH}",
|
268
|
+
"Warning")
|
269
|
+
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|
274
|
+
|
275
|
+
|
276
|
+
class EOLBackslashViolationDetector < ViolationDetector
|
277
|
+
|
278
|
+
FINAL_BACKSLASH = /\\$/
|
279
|
+
|
280
|
+
def check_line(file, line_num, line)
|
281
|
+
if FINAL_BACKSLASH =~ line
|
282
|
+
@violation_rec.violation(file, line_num, line,
|
283
|
+
"Backslash at End of Line", "Warning")
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
class MissingFinalReturnViolationDetector < ViolationDetector
|
291
|
+
|
292
|
+
FINAL_RETURN = /\n$/
|
293
|
+
|
294
|
+
def check_lines(file, lines)
|
295
|
+
if FINAL_RETURN !~ lines.last
|
296
|
+
@violation_rec.violation(file, lines.size, lines.last,
|
297
|
+
"Missing Final Return", "Warning")
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
302
|
+
|
303
|
+
|
304
|
+
class AmritaViolationReceiver
|
305
|
+
|
306
|
+
attr_reader :summary, :details
|
307
|
+
|
308
|
+
def initialize
|
309
|
+
@summary = Hash.new(0)
|
310
|
+
@details = []
|
311
|
+
@seen_files = []
|
312
|
+
end
|
313
|
+
|
314
|
+
def violation(file, line_num, line, type, level)
|
315
|
+
@summary[type] += 1
|
316
|
+
@details << {
|
317
|
+
:file => file,
|
318
|
+
:type => type,
|
319
|
+
:line_num => line_num,
|
320
|
+
:line => line.inspect
|
321
|
+
}
|
322
|
+
@seen_files << file if !@seen_files.include?(file)
|
323
|
+
end
|
324
|
+
|
325
|
+
def empty?
|
326
|
+
@details.empty?
|
327
|
+
end
|
328
|
+
|
329
|
+
def files
|
330
|
+
@seen_files.size
|
331
|
+
end
|
332
|
+
|
333
|
+
end
|
334
|
+
|
335
|
+
|
336
|
+
if __FILE__ == $0
|
337
|
+
|
338
|
+
class ViolationPrinter
|
339
|
+
attr_reader :num
|
340
|
+
|
341
|
+
def initialize()
|
342
|
+
@num = 0
|
343
|
+
end
|
344
|
+
|
345
|
+
def violation(file, line_num, line, type, level)
|
346
|
+
puts "#{file} : #{line_num} - #{level} : #{type}"
|
347
|
+
@num += 1
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
vp = ViolationPrinter.new
|
352
|
+
ca = CodeFormattingAction.new(vp)
|
353
|
+
ARGV.each do |file|
|
354
|
+
res = ca.check_formatting(file)
|
355
|
+
end
|
356
|
+
puts "Number of violations: #{vp.num}"
|
357
|
+
|
358
|
+
end
|