kwala 0.9.0
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/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
|