asautotest 0.0.1
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/LICENSE +674 -0
- data/README.rdoc +182 -0
- data/bin/asautotest +437 -0
- data/bin/flash-policy-server +88 -0
- data/lib/asautotest/compilation-output-parser.rb +115 -0
- data/lib/asautotest/compilation-result.rb +102 -0
- data/lib/asautotest/compilation-runner.rb +135 -0
- data/lib/asautotest/compiler-shell.rb +78 -0
- data/lib/asautotest/logging.rb +124 -0
- data/lib/asautotest/problematic-file.rb +526 -0
- data/lib/asautotest/stopwatch.rb +50 -0
- data/lib/asautotest/test-runner.rb +424 -0
- data/lib/asautotest/utilities.rb +62 -0
- metadata +78 -0
@@ -0,0 +1,124 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# logging.rb --- mixins for logging and various other utilities
|
3
|
+
# Copyright (C) 2010 Go Interactive
|
4
|
+
|
5
|
+
# This file is part of ASAutotest.
|
6
|
+
|
7
|
+
# ASAutotest is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
|
12
|
+
# ASAutotest is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
|
17
|
+
# You should have received a copy of the GNU General Public License
|
18
|
+
# along with ASAutotest. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
|
20
|
+
module ASAutotest
|
21
|
+
module Bracketable
|
22
|
+
def [] *arguments ; new(*arguments) end
|
23
|
+
end
|
24
|
+
|
25
|
+
module Logging
|
26
|
+
PREFIX = "asautotest: "
|
27
|
+
|
28
|
+
def self.verbose= value
|
29
|
+
@verbose = value
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.verbose?
|
33
|
+
@verbose == true
|
34
|
+
end
|
35
|
+
|
36
|
+
def verbose?
|
37
|
+
Logging.verbose?
|
38
|
+
end
|
39
|
+
|
40
|
+
# ------------------------------------------------------
|
41
|
+
|
42
|
+
def say(*arguments, &block)
|
43
|
+
if block_given?
|
44
|
+
say_with_block(*arguments, &block)
|
45
|
+
else
|
46
|
+
say_without_block(*arguments)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def shout(message)
|
51
|
+
say "\e[1;31m!!\e[0m #{message}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def hint(message)
|
55
|
+
say "\e[34m#{message}\e[0m"
|
56
|
+
end
|
57
|
+
|
58
|
+
def whisper(*arguments, &block)
|
59
|
+
if block_given?
|
60
|
+
whisper_with_block(*arguments, &block)
|
61
|
+
else
|
62
|
+
whisper_without_block(*arguments)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def barf(message)
|
67
|
+
puts "\e[1;31m??\e[0m #{message}"
|
68
|
+
end
|
69
|
+
|
70
|
+
# ------------------------------------------------------
|
71
|
+
|
72
|
+
def say_without_block(message)
|
73
|
+
puts "#{PREFIX}#{message}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def say_with_block(message, ok_message = "ok", error_message = "failed")
|
77
|
+
start_saying(message)
|
78
|
+
status = ""
|
79
|
+
yield(status)
|
80
|
+
end_saying(status.empty? ? ok_message : status)
|
81
|
+
ended = true
|
82
|
+
ensure
|
83
|
+
end_saying(error_message) unless ended
|
84
|
+
end
|
85
|
+
|
86
|
+
def start_saying(message)
|
87
|
+
STDOUT.print "#{PREFIX}#{message}..."
|
88
|
+
STDOUT.flush
|
89
|
+
end
|
90
|
+
|
91
|
+
def end_saying(message)
|
92
|
+
STDOUT.puts " #{message}."
|
93
|
+
end
|
94
|
+
|
95
|
+
# ------------------------------------------------------
|
96
|
+
|
97
|
+
def whisper_without_block(message)
|
98
|
+
say message if verbose?
|
99
|
+
end
|
100
|
+
|
101
|
+
def whisper_with_block(message, ok_message = "ok", error_message = "failed")
|
102
|
+
start_whisper(message)
|
103
|
+
yield
|
104
|
+
end_whisper(ok_message)
|
105
|
+
ended = true
|
106
|
+
ensure
|
107
|
+
end_whisper(error_message) unless ended
|
108
|
+
end
|
109
|
+
|
110
|
+
def start_whisper(message)
|
111
|
+
start_saying(message) if verbose?
|
112
|
+
end
|
113
|
+
|
114
|
+
def end_whisper(message)
|
115
|
+
end_saying(message) if verbose?
|
116
|
+
end
|
117
|
+
|
118
|
+
# ------------------------------------------------------
|
119
|
+
|
120
|
+
def new_logging_section
|
121
|
+
say("—" * 60)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,526 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# problematic-file.rb --- parsing of compiler errors and warnings
|
3
|
+
# Copyright (C) 2010 Go Interactive
|
4
|
+
|
5
|
+
# This file is part of ASAutotest.
|
6
|
+
|
7
|
+
# ASAutotest is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
|
12
|
+
# ASAutotest is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
|
17
|
+
# You should have received a copy of the GNU General Public License
|
18
|
+
# along with ASAutotest. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
|
20
|
+
module ASAutotest
|
21
|
+
class ProblematicFile
|
22
|
+
include Logging
|
23
|
+
|
24
|
+
def initialize(file_name, source_directories)
|
25
|
+
@file_name = file_name
|
26
|
+
@source_directories = source_directories
|
27
|
+
@problems = []
|
28
|
+
end
|
29
|
+
|
30
|
+
def << problem
|
31
|
+
@problems << problem
|
32
|
+
problem.file = self
|
33
|
+
@problems = @problems.sort_by { |x| x.sort_key }
|
34
|
+
end
|
35
|
+
|
36
|
+
def n_problems
|
37
|
+
@problems.size
|
38
|
+
end
|
39
|
+
|
40
|
+
def type
|
41
|
+
Type[dirname.gsub(/\/|\\/, "."), basename.sub(/\..*$/, "")]
|
42
|
+
end
|
43
|
+
|
44
|
+
def any_type_warnings?
|
45
|
+
@problems.any? &:type_warning?
|
46
|
+
end
|
47
|
+
|
48
|
+
def problem_before(next_problem)
|
49
|
+
returning nil do
|
50
|
+
previous_problem = nil
|
51
|
+
for problem in @problems do
|
52
|
+
if problem == next_problem
|
53
|
+
return previous_problem
|
54
|
+
else
|
55
|
+
previous_problem = problem
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def print_report
|
62
|
+
puts
|
63
|
+
print ljust("\e[1m#{basename}\e[0m", 40)
|
64
|
+
print " (in #{dirname})" unless dirname == "." if has_file_name?
|
65
|
+
puts
|
66
|
+
|
67
|
+
@problems.each &:print_report
|
68
|
+
end
|
69
|
+
|
70
|
+
def has_file_name?
|
71
|
+
@file_name != nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def basename
|
75
|
+
if @file_name == nil
|
76
|
+
"(compiler error)"
|
77
|
+
else
|
78
|
+
File.basename(@file_name)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def dirname
|
83
|
+
File.dirname(stripped_file_name) if has_file_name?
|
84
|
+
end
|
85
|
+
|
86
|
+
def stripped_file_name
|
87
|
+
if source_directory
|
88
|
+
@file_name[source_directory.size .. -1]
|
89
|
+
else
|
90
|
+
@file_name
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def source_directory
|
95
|
+
@source_directories.find do |directory|
|
96
|
+
@file_name.start_with? directory
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class Location
|
102
|
+
extend Bracketable
|
103
|
+
attr_reader :line_number, :column_number, :source_line
|
104
|
+
|
105
|
+
def initialize(line_number, column_number, source_line)
|
106
|
+
@line_number = line_number
|
107
|
+
@column_number = column_number
|
108
|
+
@source_line = source_line
|
109
|
+
end
|
110
|
+
|
111
|
+
def sort_key
|
112
|
+
[line_number, column_number]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class Type
|
117
|
+
extend Bracketable
|
118
|
+
attr_reader :package, :name
|
119
|
+
def initialize(package, name)
|
120
|
+
@package = package
|
121
|
+
@name = name
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.parse(input)
|
125
|
+
case input
|
126
|
+
when /^(.*):(.*)$/
|
127
|
+
Type[$1, $2]
|
128
|
+
else
|
129
|
+
Type[nil, input]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def to_s
|
134
|
+
name ? name : package
|
135
|
+
end
|
136
|
+
|
137
|
+
def full_name
|
138
|
+
"#{package}.#{name}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def == other
|
142
|
+
package == other.package and name == other.name
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class Member
|
147
|
+
extend Bracketable
|
148
|
+
attr_reader :type, :name
|
149
|
+
|
150
|
+
def initialize(type, name)
|
151
|
+
@type = type
|
152
|
+
@name = name
|
153
|
+
end
|
154
|
+
|
155
|
+
def to_s
|
156
|
+
@type ? "#{@type.name}.#@name" : "#@name"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
class Problem
|
161
|
+
attr_accessor :location
|
162
|
+
attr_accessor :file
|
163
|
+
|
164
|
+
def details ; nil end
|
165
|
+
def extra_details ; nil end
|
166
|
+
def type_warning? ; false end
|
167
|
+
|
168
|
+
def source_line
|
169
|
+
if location
|
170
|
+
build_string do |result|
|
171
|
+
result << location.source_line.trim
|
172
|
+
result << " ..." unless location.source_line =~ /[;{}]\s*$/
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def sort_key
|
178
|
+
location ? location.sort_key : [0]
|
179
|
+
end
|
180
|
+
|
181
|
+
def line_number
|
182
|
+
location.line_number rescue "?"
|
183
|
+
end
|
184
|
+
|
185
|
+
def column_number
|
186
|
+
location.column_number - indentation_width rescue "?"
|
187
|
+
end
|
188
|
+
|
189
|
+
def indentation_width
|
190
|
+
location.source_line =~ /^(\s*)/ ; $1.size
|
191
|
+
end
|
192
|
+
|
193
|
+
def self.[] message, location
|
194
|
+
returning parse(message) do |problem|
|
195
|
+
problem.location = location
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.parse(message)
|
200
|
+
case message.sub(/^(Error|Warning):\s+/, "")
|
201
|
+
when /^Definition (\S+) could not be found.$/i
|
202
|
+
UndefinedImport.new(Type.parse($1))
|
203
|
+
when /^Call to a possibly undefined method (\S+) .* type (\S+).$/i
|
204
|
+
UndefinedMethod.new(Member[Type.parse($2), $1])
|
205
|
+
when /^Call to a possibly undefined method (\S+).$/i
|
206
|
+
UndefinedMethod.new(Member[nil, $1])
|
207
|
+
when /^Access of undefined property (\S+).$/i
|
208
|
+
UndefinedProperty.new(Member[nil, $1])
|
209
|
+
when /^Access of possibly undefined property (\S+)/i
|
210
|
+
UndefinedProperty.new(Member[nil, $1])
|
211
|
+
when /^Attempted access of inaccessible property (\S+)/i
|
212
|
+
InaccessibleProperty.new(Member[nil, $1])
|
213
|
+
when /^A file found in a source-path must have .*? '(\S+?)'/i
|
214
|
+
WrongPackage.new(Type[$1, nil])
|
215
|
+
when /^A file found in a source-path '(\S+?)' must .* as the class/i
|
216
|
+
WrongClassName.new(Type[nil, $1])
|
217
|
+
when /^Type was not found or was not a compile-time constant: (\S+).$/i
|
218
|
+
UndefinedType.new(Type.parse($1))
|
219
|
+
when /^The definition of base class (\S+) was not found.$/i
|
220
|
+
UndefinedType.new(Type.parse($1))
|
221
|
+
when /^Illegal assignment to a variable specified as constant.$/i
|
222
|
+
ConstantAssignment.new
|
223
|
+
when /^Method marked override must override another method.$/i
|
224
|
+
BogusOverride.new
|
225
|
+
when /^Incorrect number of arguments.\s* Expected no more than (\d+).$/i
|
226
|
+
TooManyArguments.new($1.to_i)
|
227
|
+
when /^Incorrect number of arguments.\s* Expected (0).$/i
|
228
|
+
TooManyArguments.new($1.to_i)
|
229
|
+
when /^Incorrect number of arguments.\s* Expected (\d+).$/i
|
230
|
+
TooFewArguments.new($1.to_i)
|
231
|
+
when /^return value for function '(\S+)' has no type declaration.$/i
|
232
|
+
MissingReturnType.new(Member[nil, $1])
|
233
|
+
when /^(?:variable|parameter) '(\S+)' has no type declaration.$/i
|
234
|
+
MissingTypeDeclaration.new
|
235
|
+
when /^Interface (\S+) was not found.$/i
|
236
|
+
InterfaceNotFound.new(Type.parse($1))
|
237
|
+
when /^Implicit coercion of a value of type (\S+) to an unrelated type (\S+).$/i
|
238
|
+
TypeMismatch.new(Type.parse($2), Type.parse($1))
|
239
|
+
when /^Comparison between a value with static type (\S+) and a possibly unrelated type Null.$/i
|
240
|
+
InvalidNullComparison.new
|
241
|
+
when /^Interface method ((?:get |set )?\S+) in namespace (\S+) not implemented by class (\S+).$/i
|
242
|
+
MissingImplementation.new(Member[Type.parse($2), $1], Type.parse($3))
|
243
|
+
when /^Interface method ((?:get |set )?\S+) in namespace (\S+) is implemented with an incompatible signature in class (\S+).$/i
|
244
|
+
WrongImplementation.new(Member[Type.parse($2), $1], Type.parse($3))
|
245
|
+
when /^A file (.*) must have an externally visible definition./
|
246
|
+
MissingPublicDefinition.new
|
247
|
+
else
|
248
|
+
Unknown.new(message)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def detail ; nil end
|
253
|
+
def plain_message
|
254
|
+
if detail
|
255
|
+
"#{message} #{detail}"
|
256
|
+
else
|
257
|
+
message.sub(/:$/, ".")
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
class ConstantAssignment < Problem
|
262
|
+
def message ; "Attempt to modify constant:" end
|
263
|
+
def details ; identifier_source_line_details end
|
264
|
+
end
|
265
|
+
|
266
|
+
class BogusOverride < Problem
|
267
|
+
def message ; "Bogus override:" end
|
268
|
+
def details ; identifier_source_line_details end
|
269
|
+
end
|
270
|
+
|
271
|
+
class UndefinedImport < Problem
|
272
|
+
def initialize(type) @type = type end
|
273
|
+
def message ; "Import not found:" end
|
274
|
+
def details ; bullet_details(detail) end
|
275
|
+
def detail ; @type.full_name end
|
276
|
+
end
|
277
|
+
|
278
|
+
class UndefinedMethod < Problem
|
279
|
+
def initialize(member) @member = member end
|
280
|
+
def message ; "Undefined method:" end
|
281
|
+
def details ; identifier_source_line_details end
|
282
|
+
def detail ; @member end
|
283
|
+
end
|
284
|
+
|
285
|
+
class UndefinedProperty < Problem
|
286
|
+
def initialize(member) @member = member end
|
287
|
+
def message ; "Undefined property:" end
|
288
|
+
def details ; identifier_source_line_details end
|
289
|
+
def detail ; @member end
|
290
|
+
end
|
291
|
+
|
292
|
+
class InaccessibleProperty < Problem
|
293
|
+
def initialize(member) @member = member end
|
294
|
+
def message ; "Property access not allowed:" end
|
295
|
+
def details ; identifier_source_line_details end
|
296
|
+
def detail ; @member end
|
297
|
+
end
|
298
|
+
|
299
|
+
class WrongPackage < Problem
|
300
|
+
def initialize(type) @type = type end
|
301
|
+
def message ; "Package should be #@type." end
|
302
|
+
end
|
303
|
+
|
304
|
+
class WrongClassName < Problem
|
305
|
+
def initialize(type) @type = type end
|
306
|
+
def message ; "Class name should be #@type." end
|
307
|
+
end
|
308
|
+
|
309
|
+
class UndefinedType < Problem
|
310
|
+
def initialize(type) @type = type end
|
311
|
+
def message ; "Undefined type:" end
|
312
|
+
def details ; identifier_source_line_details end
|
313
|
+
def detail ; @type.full_name end
|
314
|
+
def extra_details
|
315
|
+
if problematic_identifier == "Sprite"
|
316
|
+
"Hint: It’s flash.display.Sprite."
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
class TooManyArguments < Problem
|
322
|
+
def initialize(max) @max = max end
|
323
|
+
def message
|
324
|
+
if @max == 0
|
325
|
+
"No arguments allowed:"
|
326
|
+
else
|
327
|
+
"Too many arguments (only #@max allowed):"
|
328
|
+
end
|
329
|
+
end
|
330
|
+
def details ; source_line_details end
|
331
|
+
end
|
332
|
+
|
333
|
+
class TooFewArguments < Problem
|
334
|
+
def initialize(min) @min = min end
|
335
|
+
def message ; "Too few arguments (expected #@min):" end
|
336
|
+
def details ; source_line_details end
|
337
|
+
end
|
338
|
+
|
339
|
+
class MissingReturnType < Problem
|
340
|
+
def initialize(member) @member = member end
|
341
|
+
def message ; "Missing return type:" end
|
342
|
+
def detail ; @member end
|
343
|
+
def details ; member_details end
|
344
|
+
def type_warning? ; true end
|
345
|
+
end
|
346
|
+
|
347
|
+
class MissingTypeDeclaration < Problem
|
348
|
+
def message ; "Missing type:" end
|
349
|
+
def details ; identifier_source_line_details end
|
350
|
+
def type_warning? ; true end
|
351
|
+
end
|
352
|
+
|
353
|
+
class InterfaceNotFound < Problem
|
354
|
+
def initialize(member) @member = member end
|
355
|
+
def message ; "Interface not found:" end
|
356
|
+
def detail ; @member end
|
357
|
+
def details ; member_details end
|
358
|
+
end
|
359
|
+
|
360
|
+
class InvalidNullComparison < Problem
|
361
|
+
def message ; "Invalid comparison to null." end
|
362
|
+
def details ; source_line_details end
|
363
|
+
end
|
364
|
+
|
365
|
+
class TypeMismatch < Problem
|
366
|
+
def initialize(expected_type, actual_type)
|
367
|
+
@expected_type = expected_type
|
368
|
+
@actual_type = actual_type
|
369
|
+
end
|
370
|
+
|
371
|
+
def message
|
372
|
+
"Expected \e[4m#@expected_type\e[0m " +
|
373
|
+
"but was \e[4m#@actual_type\e[0m:"
|
374
|
+
end
|
375
|
+
|
376
|
+
def details
|
377
|
+
identifier_source_line_details
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
class ImplementationProblem < Problem
|
382
|
+
def initialize(member, implementing_type)
|
383
|
+
@member = member
|
384
|
+
@implementing_type = implementing_type
|
385
|
+
end
|
386
|
+
|
387
|
+
def message
|
388
|
+
if file.type == @implementing_type
|
389
|
+
"#{core_message}:"
|
390
|
+
else
|
391
|
+
"#{core_message} in #@implementing_type:"
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
def detail ; @member end
|
396
|
+
|
397
|
+
def details
|
398
|
+
"\e[0m * \e[1m#{@member.name}\e[0m (#{@member.type})\e[0m"
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
class MissingImplementation < ImplementationProblem
|
403
|
+
def core_message
|
404
|
+
"Missing implementation"
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
class WrongImplementation < ImplementationProblem
|
409
|
+
def core_message
|
410
|
+
"Wrong implementation"
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
class MissingPublicDefinition < Problem
|
415
|
+
def message ; "Missing public class or function definition." end
|
416
|
+
end
|
417
|
+
|
418
|
+
class Unknown < Problem
|
419
|
+
def initialize(message) @message = message end
|
420
|
+
|
421
|
+
def message
|
422
|
+
@message.sub(/^Error:\s+/, "")
|
423
|
+
end
|
424
|
+
|
425
|
+
def details
|
426
|
+
if source_line
|
427
|
+
source_line_details
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
def print_report
|
433
|
+
unless message == last_message
|
434
|
+
print message_column
|
435
|
+
print dummy_line_number_column unless message_column_overflowed?
|
436
|
+
puts
|
437
|
+
end
|
438
|
+
|
439
|
+
if details and details != last_details
|
440
|
+
print details_column
|
441
|
+
print line_number_column
|
442
|
+
puts
|
443
|
+
end
|
444
|
+
|
445
|
+
if extra_details and extra_details != last_extra_details
|
446
|
+
for line in extra_details
|
447
|
+
puts "\e[34m #{line.chomp}\e[0m"
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
MESSAGE_COLUMN_WIDTH = 56
|
453
|
+
LINE_NUMBER_COLUMN_WIDTH = 4
|
454
|
+
|
455
|
+
def message_column_overflowed?
|
456
|
+
message_column.size > MESSAGE_COLUMN_WIDTH
|
457
|
+
end
|
458
|
+
|
459
|
+
def message_column
|
460
|
+
ljust(" #{message}", MESSAGE_COLUMN_WIDTH)
|
461
|
+
end
|
462
|
+
|
463
|
+
def details_column
|
464
|
+
ljust(" #{details} ", MESSAGE_COLUMN_WIDTH)
|
465
|
+
end
|
466
|
+
|
467
|
+
def line_number_column
|
468
|
+
rjust("\e[1m#{line_number}\e[0m", LINE_NUMBER_COLUMN_WIDTH)
|
469
|
+
end
|
470
|
+
|
471
|
+
def dummy_line_number_column
|
472
|
+
rjust("\e[0m|\e[0m", LINE_NUMBER_COLUMN_WIDTH)
|
473
|
+
end
|
474
|
+
|
475
|
+
def last_message
|
476
|
+
previous.message if previous
|
477
|
+
end
|
478
|
+
|
479
|
+
def last_details
|
480
|
+
previous.details if previous
|
481
|
+
end
|
482
|
+
|
483
|
+
def last_extra_details
|
484
|
+
previous.extra_details if previous
|
485
|
+
end
|
486
|
+
|
487
|
+
def previous
|
488
|
+
file.problem_before(self)
|
489
|
+
end
|
490
|
+
|
491
|
+
def source_line_details
|
492
|
+
"\e[0m ... #{source_line}\e[0m"
|
493
|
+
end
|
494
|
+
|
495
|
+
def identifier_source_line_details
|
496
|
+
"\e[0m ... #{problematic_identifier_pre}" +
|
497
|
+
"\e[1;4m#{problematic_identifier}\e[0m" +
|
498
|
+
"#{problematic_identifier_post}"
|
499
|
+
end
|
500
|
+
|
501
|
+
def problematic_identifier_pre
|
502
|
+
identifier_source_line_parts[0]
|
503
|
+
end
|
504
|
+
|
505
|
+
def problematic_identifier
|
506
|
+
identifier_source_line_parts[1]
|
507
|
+
end
|
508
|
+
|
509
|
+
def problematic_identifier_post
|
510
|
+
identifier_source_line_parts[2]
|
511
|
+
end
|
512
|
+
|
513
|
+
def identifier_source_line_parts
|
514
|
+
source_line =~ /^(.{#{column_number}})([\w$]+)(.*)$/
|
515
|
+
[$1, $2, $3]
|
516
|
+
end
|
517
|
+
|
518
|
+
def member_details
|
519
|
+
bullet_details(@member)
|
520
|
+
end
|
521
|
+
|
522
|
+
def bullet_details(content)
|
523
|
+
"\e[0m * #{content}\e[0m"
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|