cuke_sniffer 0.0.6 → 0.0.7
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/bin/cuke_sniffer +6 -1
- data/lib/cuke_sniffer/cli.rb +35 -14
- data/lib/cuke_sniffer/cuke_sniffer_helper.rb +165 -164
- data/lib/cuke_sniffer/formatter.rb +2 -19
- data/lib/cuke_sniffer/report/css.html.erb +133 -0
- data/lib/cuke_sniffer/report/dead_steps.html.erb +62 -35
- data/lib/cuke_sniffer/report/features.html.erb +104 -50
- data/lib/cuke_sniffer/report/hooks.html.erb +78 -48
- data/lib/cuke_sniffer/report/improvement_list.html.erb +12 -6
- data/lib/cuke_sniffer/report/js.html.erb +65 -0
- data/lib/cuke_sniffer/report/min_template.html.erb +16 -125
- data/lib/cuke_sniffer/report/rules.html.erb +75 -8
- data/lib/cuke_sniffer/report/standard_template.html.erb +14 -127
- data/lib/cuke_sniffer/report/step_definitions.html.erb +68 -49
- data/lib/cuke_sniffer/report/summary.html.erb +40 -15
- data/lib/cuke_sniffer/report/title.html.erb +9 -0
- data/lib/cuke_sniffer/rule_config.rb +508 -483
- metadata +5 -3
- data/lib/cuke_sniffer/report/sub_rules.html.erb +0 -24
@@ -1,49 +1,68 @@
|
|
1
|
-
<div class="title" onclick="toggleById('step_definitions_data', this)">
|
2
|
-
Step Definitions +
|
3
|
-
</div>
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
<% cuke_sniffer.step_definitions.
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
<%
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
<
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
1
|
+
<div class="title" onclick="toggleById('step_definitions_data', this);updateDisplayStatus(document.getElementById('step_definition_function_bar'));updateDivScroll('step_definitions_data', 'step_definitions_table');">
|
2
|
+
Step Definitions +
|
3
|
+
</div>
|
4
|
+
|
5
|
+
<div id="step_definition_function_bar" class="function_bar">
|
6
|
+
<input type="button" class="function_button" value="Collapse All" onclick="collapseAll('step_definition_detail')" />
|
7
|
+
<input type="button" class="function_button" value="Expand All" onclick="expandAllShiftingRows('step_definition_detail')" />
|
8
|
+
</div>
|
9
|
+
|
10
|
+
<div class="new_sub_section" id="step_definitions_data">
|
11
|
+
<% if cuke_sniffer.step_definitions.count == 0 %>
|
12
|
+
<div class="empty_set_message">There were no Step Definitions to sniff in '<%= cuke_sniffer.step_definitions_location %>'!</div>
|
13
|
+
<% elsif cuke_sniffer.step_definitions.count >= 1 && cuke_sniffer.summary[:step_definitions][:total_score] ==0 %>
|
14
|
+
<div class="empty_set_message">Excellent! No smells found for Step Definitions!</div>
|
15
|
+
<% end %>
|
16
|
+
<table id="step_definitions_table" style="width:100%;" border="0" cellspacing="0">
|
17
|
+
<% index = 0 %>
|
18
|
+
<% cuke_sniffer.step_definitions.each do |step_definition| %>
|
19
|
+
<% next if step_definition.score <= 0 %>
|
20
|
+
<% if index.odd? %>
|
21
|
+
<tr class="notes blue_title_row" onclick="updateDisplayStatus(document.getElementById('step_definition_detail_<%=index%>')); updateDivScroll('step_definitions_data', 'step_definitions_table');">
|
22
|
+
<% else %>
|
23
|
+
<tr class="notes white_title_row" onclick="updateDisplayStatus(document.getElementById('step_definition_detail_<%=index%>')); updateDivScroll('step_definitions_data', 'step_definitions_table');">
|
24
|
+
<% end %>
|
25
|
+
<td id="step_definition_score_<%= index %>" valign="top" style="width:1%; color: red">
|
26
|
+
<%= step_definition.score %>
|
27
|
+
</td>
|
28
|
+
<td id="step_definition_regex_<%= index %>" style="text-indent: 2px;">
|
29
|
+
/<%= step_definition.regex.to_s.gsub(/[(]\?-mix:/, "")[0...-1] %>/
|
30
|
+
</td>
|
31
|
+
<td id="step_definition_calls_<%= index %>" valign="top" style="text-align:right;width:8%;">
|
32
|
+
<% if cuke_sniffer.cataloged? %>
|
33
|
+
Call(s): <%= step_definition.calls.size %>
|
34
|
+
<% else %>
|
35
|
+
Not cataloged
|
36
|
+
<% end %>
|
37
|
+
</td>
|
38
|
+
</tr>
|
39
|
+
|
40
|
+
<% if index.odd? %>
|
41
|
+
<tr id="step_definition_detail_<%= index %>" class="blue_row step_definition_detail" style="display: none;">
|
42
|
+
<% else %>
|
43
|
+
<tr id="step_definition_detail_<%= index %>" class="white_row step_definition_detail" style="display: none;">
|
44
|
+
<% end %>
|
45
|
+
<td colspan="2">
|
46
|
+
<% SummaryHelper::sort_improvement_list(step_definition.rules_hash).each do |phrase, count| %>
|
47
|
+
<div style="text-indent: 10px;">
|
48
|
+
<div style="color:red; display:inline-block;">
|
49
|
+
<%= count %>
|
50
|
+
</div>
|
51
|
+
<div style="display:inline-block;">
|
52
|
+
<%= phrase %>
|
53
|
+
</div>
|
54
|
+
</div>
|
55
|
+
<% end %>
|
56
|
+
</td>
|
57
|
+
<td valign="top" style="text-align: right">
|
58
|
+
<a href="file:///<%= step_definition.location.gsub(/:\d+$/, "") %>" title="Note: Links to file on system this report was generated.">
|
59
|
+
<%= step_definition.location.gsub(cuke_sniffer.step_definitions_location, '') %>
|
60
|
+
</a>
|
61
|
+
</td>
|
62
|
+
</tr>
|
63
|
+
|
64
|
+
<% index += 1 %>
|
65
|
+
<% end %>
|
66
|
+
</table>
|
67
|
+
</div>
|
68
|
+
<br style="clear:both">
|
@@ -3,7 +3,7 @@
|
|
3
3
|
</div>
|
4
4
|
<div class="shrink_section" style="display: block;" id="summary_data">
|
5
5
|
<% data_symbols = [:features, :scenarios, :step_definitions, :hooks] %>
|
6
|
-
<table style="width: 100%;" class="notes">
|
6
|
+
<table style="padding-left:2px; width: 100%;" class="notes" border="0" cellspacing="0">
|
7
7
|
<tr class="divide_row">
|
8
8
|
<td class="table_top"></td>
|
9
9
|
<td class="table_top">Features</td>
|
@@ -11,47 +11,69 @@
|
|
11
11
|
<td class="table_top">Step Definitions</td>
|
12
12
|
<td class="table_top">Hooks</td>
|
13
13
|
</tr>
|
14
|
-
<tr>
|
15
|
-
<td>
|
14
|
+
<tr class="blue_row">
|
15
|
+
<td>
|
16
|
+
Total Score
|
17
|
+
<a title="Total Points for a section." class="help">(?)</a>
|
18
|
+
</td>
|
16
19
|
<% data_symbols.each do |symbol| %>
|
17
|
-
<td
|
20
|
+
<td>
|
21
|
+
<%= cuke_sniffer.summary[symbol][:total_score] %>
|
22
|
+
</td>
|
18
23
|
<% end %>
|
19
24
|
</tr>
|
20
25
|
<tr>
|
21
|
-
<td>
|
26
|
+
<td>
|
27
|
+
Count
|
28
|
+
<a title="Total Number of objects found in the project." class="help" class="help">(?)</a>
|
29
|
+
</td>
|
22
30
|
<% data_symbols.each do |symbol| %>
|
23
31
|
<td><%= cuke_sniffer.summary[symbol][:total].inspect %></td>
|
24
32
|
<% end %>
|
25
33
|
</tr>
|
26
34
|
<tr/>
|
27
|
-
<tr>
|
28
|
-
<td>
|
35
|
+
<tr class="blue_row">
|
36
|
+
<td>
|
37
|
+
Lowest Score
|
38
|
+
<a title="Score of the best object found." class="help">(?)</a>
|
39
|
+
</td>
|
29
40
|
<% data_symbols.each do |symbol| %>
|
30
41
|
<td><%= cuke_sniffer.summary[symbol][:min] %></td>
|
31
42
|
<% end %>
|
32
43
|
</tr>
|
33
44
|
<tr>
|
34
|
-
<td>
|
45
|
+
<td>
|
46
|
+
Highest Score
|
47
|
+
<a title="Score of the object with the most to improve on." class="help">(?)</a>
|
48
|
+
</td>
|
35
49
|
<% data_symbols.each do |symbol| %>
|
36
50
|
<td><%= cuke_sniffer.summary[symbol][:max] %></td>
|
37
51
|
<% end %>
|
38
52
|
</tr>
|
39
|
-
<tr>
|
40
|
-
<td>
|
53
|
+
<tr class="blue_row">
|
54
|
+
<td>
|
55
|
+
Average Score
|
56
|
+
<a title="Average score of a section." class="help">(?)</a>
|
57
|
+
</td>
|
41
58
|
<% data_symbols.each do |symbol| %>
|
42
59
|
<td><%= cuke_sniffer.summary[symbol][:average] %></td>
|
43
60
|
<% end %>
|
44
61
|
</tr>
|
45
62
|
<tr/>
|
46
|
-
|
47
63
|
<tr>
|
48
|
-
<td>
|
64
|
+
<td>
|
65
|
+
Threshold
|
66
|
+
<a title="The maximum score an object can have to be considered good." class="help">(?)</a>
|
67
|
+
</td>
|
49
68
|
<% data_symbols.each do |symbol| %>
|
50
69
|
<td><%= cuke_sniffer.summary[symbol][:threshold] %></td>
|
51
70
|
<% end %>
|
52
71
|
</tr>
|
53
|
-
<tr>
|
54
|
-
<td>
|
72
|
+
<tr class="blue_row">
|
73
|
+
<td>
|
74
|
+
Good
|
75
|
+
<a title="Percentage of the objects that can be considered good in the project." class="help">(?)</a>
|
76
|
+
</td>
|
55
77
|
<% data_symbols.each do |symbol| %>
|
56
78
|
<td><%= ((cuke_sniffer.summary[symbol][:good].to_f/cuke_sniffer.summary[symbol][:total].to_f) * 100).round(2) %>
|
57
79
|
%
|
@@ -59,7 +81,10 @@
|
|
59
81
|
<% end %>
|
60
82
|
</tr>
|
61
83
|
<tr>
|
62
|
-
<td>
|
84
|
+
<td>
|
85
|
+
Bad
|
86
|
+
<a title="Percentage of the objects that can be considered bad in the project." class="help">(?)</a>
|
87
|
+
</td>
|
63
88
|
<% data_symbols.each do |symbol| %>
|
64
89
|
<td><%= ((cuke_sniffer.summary[symbol][:bad].to_f/cuke_sniffer.summary[symbol][:total].to_f) * 100).round(2) %>
|
65
90
|
%
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<div style="font-family: Arial; float: left; color: #509f50; font-size: 20px; font-weight: bold; padding-bottom: 1px;">
|
2
|
+
Cuke Sniffer
|
3
|
+
</div>
|
4
|
+
<br style="clear:both">
|
5
|
+
<div style="border-top: 2px solid #509f50; text-align: right;">
|
6
|
+
<div style="font-family: Arial;border-bottom-left-radius: 15px; border-bottom-right-radius: 15px;float: right; background-color:#509f50; color:white;padding-left:40px;font-size:11pt;font-weight:bold;padding-right:10px;">
|
7
|
+
Score: <%= cuke_sniffer.summary[:total_score] %></div>
|
8
|
+
</div>
|
9
|
+
<br style="clear:both">
|
@@ -1,483 +1,508 @@
|
|
1
|
-
module CukeSniffer
|
2
|
-
|
3
|
-
# Contains the rules and various scores used in evaluating objects
|
4
|
-
module RuleConfig
|
5
|
-
|
6
|
-
# Will prevent suite from executing properly
|
7
|
-
FATAL = 100
|
8
|
-
|
9
|
-
# Will cause problem with debugging
|
10
|
-
ERROR = 25
|
11
|
-
|
12
|
-
# Readability/misuse of cucumber
|
13
|
-
WARNING = 10
|
14
|
-
|
15
|
-
# Small improvements that can be made
|
16
|
-
INFO = 1
|
17
|
-
|
18
|
-
fatal_rules = {
|
19
|
-
:no_examples => {
|
20
|
-
:enabled => true,
|
21
|
-
:phrase => "Scenario Outline with no examples.",
|
22
|
-
:score => FATAL,
|
23
|
-
:targets => ["Scenario"],
|
24
|
-
:reason => "object.type == \"Scenario Outline\" and object.examples_table.size == 1"
|
25
|
-
},
|
26
|
-
:no_examples_table => {
|
27
|
-
:enabled => true,
|
28
|
-
:phrase => "Scenario Outline with no examples table.",
|
29
|
-
:score => FATAL,
|
30
|
-
:targets => ["Scenario"],
|
31
|
-
:reason => "object.type == \"Scenario Outline\" and object.examples_table.empty?"
|
32
|
-
},
|
33
|
-
:recursive_nested_step => {
|
34
|
-
:enabled => true,
|
35
|
-
:phrase => "Recursive nested step call.",
|
36
|
-
:score => FATAL,
|
37
|
-
:targets => ["StepDefinition"],
|
38
|
-
:reason => "object.nested_steps.each_value {|nested_step| store_rule(object, rule) if nested_step =~ object.regex}"
|
39
|
-
},
|
40
|
-
:background_with_tag => {
|
41
|
-
:enabled => true,
|
42
|
-
:phrase => "There is a background with a tag. This feature file cannot run!",
|
43
|
-
:score => FATAL,
|
44
|
-
:targets => ["Background"],
|
45
|
-
:reason => "object.tags.size > 0"
|
46
|
-
},
|
47
|
-
:comment_after_tag => {
|
48
|
-
:enabled => true,
|
49
|
-
:phrase => "Comment comes between tag and properly executing line. This feature file cannot run!",
|
50
|
-
:score => FATAL,
|
51
|
-
:targets => ["Feature", "Scenario"],
|
52
|
-
:reason => "flag = false
|
53
|
-
unless object.tags.empty?
|
54
|
-
object.tags[1..-1].each do | tags |
|
55
|
-
if tags =~ /^\\s*\\#.*$/
|
56
|
-
flag = true
|
57
|
-
break
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
flag"
|
62
|
-
}
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
:
|
68
|
-
:
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
}
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
:
|
78
|
-
:
|
79
|
-
|
80
|
-
|
81
|
-
:
|
82
|
-
|
83
|
-
|
84
|
-
:
|
85
|
-
:
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
:
|
92
|
-
:
|
93
|
-
:
|
94
|
-
:
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
:
|
101
|
-
:
|
102
|
-
:
|
103
|
-
:
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
:
|
110
|
-
:
|
111
|
-
|
112
|
-
|
113
|
-
:
|
114
|
-
|
115
|
-
|
116
|
-
:
|
117
|
-
:
|
118
|
-
|
119
|
-
|
120
|
-
:
|
121
|
-
|
122
|
-
|
123
|
-
:
|
124
|
-
:
|
125
|
-
|
126
|
-
|
127
|
-
:
|
128
|
-
|
129
|
-
|
130
|
-
:
|
131
|
-
:
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
:
|
154
|
-
:
|
155
|
-
:
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
},
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
:
|
184
|
-
:
|
185
|
-
|
186
|
-
|
187
|
-
:
|
188
|
-
|
189
|
-
|
190
|
-
:
|
191
|
-
:
|
192
|
-
|
193
|
-
|
194
|
-
:
|
195
|
-
|
196
|
-
|
197
|
-
:
|
198
|
-
:
|
199
|
-
|
200
|
-
|
201
|
-
:
|
202
|
-
|
203
|
-
|
204
|
-
:
|
205
|
-
:
|
206
|
-
:
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
:
|
212
|
-
:
|
213
|
-
:
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
:
|
222
|
-
:
|
223
|
-
:
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
:
|
232
|
-
:
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
},
|
237
|
-
:
|
238
|
-
:enabled => true,
|
239
|
-
:phrase => "
|
240
|
-
:score => WARNING,
|
241
|
-
:targets => ["Scenario"],
|
242
|
-
:reason => "object.
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
:
|
249
|
-
:
|
250
|
-
:
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
:
|
256
|
-
:
|
257
|
-
:
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
},
|
262
|
-
:
|
263
|
-
:enabled => true,
|
264
|
-
:phrase => "
|
265
|
-
:score => WARNING,
|
266
|
-
:
|
267
|
-
:
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
:
|
274
|
-
:
|
275
|
-
:
|
276
|
-
:
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
:
|
283
|
-
:
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
:
|
292
|
-
:
|
293
|
-
:
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
:
|
305
|
-
:
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
:
|
331
|
-
:
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
:
|
338
|
-
:
|
339
|
-
:
|
340
|
-
:
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
:
|
347
|
-
:
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
:
|
355
|
-
:
|
356
|
-
:
|
357
|
-
:
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
:
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
:
|
379
|
-
:
|
380
|
-
:
|
381
|
-
:
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
:
|
387
|
-
:
|
388
|
-
:
|
389
|
-
:
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
:
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
:
|
410
|
-
:
|
411
|
-
:
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
:enabled => true,
|
422
|
-
:phrase => "
|
423
|
-
:score => INFO,
|
424
|
-
:
|
425
|
-
:
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
:
|
430
|
-
:
|
431
|
-
:
|
432
|
-
:targets => ["
|
433
|
-
:reason => "object.
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
:
|
444
|
-
:
|
445
|
-
:
|
446
|
-
:targets => ["StepDefinition"],
|
447
|
-
:reason => "object.code.each
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
},
|
463
|
-
:
|
464
|
-
:enabled => true,
|
465
|
-
:phrase => "
|
466
|
-
:score => INFO,
|
467
|
-
:
|
468
|
-
:targets => ["
|
469
|
-
:reason => "object.
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
1
|
+
module CukeSniffer
|
2
|
+
|
3
|
+
# Contains the rules and various scores used in evaluating objects
|
4
|
+
module RuleConfig
|
5
|
+
|
6
|
+
# Will prevent suite from executing properly
|
7
|
+
FATAL = 100
|
8
|
+
|
9
|
+
# Will cause problem with debugging
|
10
|
+
ERROR = 25
|
11
|
+
|
12
|
+
# Readability/misuse of cucumber
|
13
|
+
WARNING = 10
|
14
|
+
|
15
|
+
# Small improvements that can be made
|
16
|
+
INFO = 1
|
17
|
+
|
18
|
+
fatal_rules = {
|
19
|
+
:no_examples => {
|
20
|
+
:enabled => true,
|
21
|
+
:phrase => "Scenario Outline with no examples.",
|
22
|
+
:score => FATAL,
|
23
|
+
:targets => ["Scenario"],
|
24
|
+
:reason => "object.type == \"Scenario Outline\" and object.examples_table.size == 1"
|
25
|
+
},
|
26
|
+
:no_examples_table => {
|
27
|
+
:enabled => true,
|
28
|
+
:phrase => "Scenario Outline with no examples table.",
|
29
|
+
:score => FATAL,
|
30
|
+
:targets => ["Scenario"],
|
31
|
+
:reason => "object.type == \"Scenario Outline\" and object.examples_table.empty?"
|
32
|
+
},
|
33
|
+
:recursive_nested_step => {
|
34
|
+
:enabled => true,
|
35
|
+
:phrase => "Recursive nested step call.",
|
36
|
+
:score => FATAL,
|
37
|
+
:targets => ["StepDefinition"],
|
38
|
+
:reason => "object.nested_steps.each_value {|nested_step| store_rule(object, rule) if nested_step =~ object.regex}"
|
39
|
+
},
|
40
|
+
:background_with_tag => {
|
41
|
+
:enabled => true,
|
42
|
+
:phrase => "There is a background with a tag. This feature file cannot run!",
|
43
|
+
:score => FATAL,
|
44
|
+
:targets => ["Background"],
|
45
|
+
:reason => "object.tags.size > 0"
|
46
|
+
},
|
47
|
+
:comment_after_tag => {
|
48
|
+
:enabled => true,
|
49
|
+
:phrase => "Comment comes between tag and properly executing line. This feature file cannot run!",
|
50
|
+
:score => FATAL,
|
51
|
+
:targets => ["Feature", "Scenario"],
|
52
|
+
:reason => "flag = false
|
53
|
+
unless object.tags.empty?
|
54
|
+
object.tags[1..-1].each do | tags |
|
55
|
+
if tags =~ /^\\s*\\#.*$/
|
56
|
+
flag = true
|
57
|
+
break
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
flag"
|
62
|
+
},
|
63
|
+
:universal_nested_step => {
|
64
|
+
:enabled => true,
|
65
|
+
:phrase => "A nested step should not universally match all step definitions. Dead steps cannot be correctly cataloged.",
|
66
|
+
:score => FATAL,
|
67
|
+
:targets => ["StepDefinition"],
|
68
|
+
:reason => "object.nested_steps.each_value do | step_value |
|
69
|
+
modified_step = step_value.gsub(/\\\#{[^}]*}/, '.*')
|
70
|
+
store_rule(object, rule) if modified_step == '.*'
|
71
|
+
end"
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
error_rules = {
|
76
|
+
:no_description => {
|
77
|
+
:enabled => true,
|
78
|
+
:phrase => "{class} has no description.",
|
79
|
+
:score => ERROR,
|
80
|
+
:targets => ["Feature", "Scenario"],
|
81
|
+
:reason => "object.name.empty?"
|
82
|
+
},
|
83
|
+
:no_scenarios => {
|
84
|
+
:enabled => true,
|
85
|
+
:phrase => "Feature with no scenarios.",
|
86
|
+
:score => ERROR,
|
87
|
+
:targets => ["Feature"],
|
88
|
+
:reason => "object.scenarios.empty?"
|
89
|
+
},
|
90
|
+
:commented_step => {
|
91
|
+
:enabled => true,
|
92
|
+
:phrase => "Commented step.",
|
93
|
+
:score => ERROR,
|
94
|
+
:targets => ["Scenario", "Background"],
|
95
|
+
:reason => "object.steps.each do |step|
|
96
|
+
store_rule(object, rule) if is_comment?(step)
|
97
|
+
end"
|
98
|
+
},
|
99
|
+
:commented_example => {
|
100
|
+
:enabled => true,
|
101
|
+
:phrase => "Commented example.",
|
102
|
+
:score => ERROR,
|
103
|
+
:targets => ["Scenario"],
|
104
|
+
:reason => "if object.type == 'Scenario Outline'
|
105
|
+
object.examples_table.each {|example| store_rule(object, rule) if is_comment?(example)}
|
106
|
+
end"
|
107
|
+
},
|
108
|
+
:no_steps => {
|
109
|
+
:enabled => true,
|
110
|
+
:phrase => "No steps in Scenario.",
|
111
|
+
:score => ERROR,
|
112
|
+
:targets => ["Scenario", "Background"],
|
113
|
+
:reason => "object.steps.empty?"
|
114
|
+
},
|
115
|
+
:one_word_step => {
|
116
|
+
:enabled => true,
|
117
|
+
:phrase => "Step that is only one word long.",
|
118
|
+
:score => ERROR,
|
119
|
+
:targets => ["Scenario", "Background"],
|
120
|
+
:reason => "object.steps.each {|step| store_rule(object, rule) if step.split.count == 2}"
|
121
|
+
},
|
122
|
+
:no_code => {
|
123
|
+
:enabled => true,
|
124
|
+
:phrase => "No code in Step Definition.",
|
125
|
+
:score => ERROR,
|
126
|
+
:targets => ["StepDefinition"],
|
127
|
+
:reason => "object.code.empty?"
|
128
|
+
},
|
129
|
+
:around_hook_without_2_parameters => {
|
130
|
+
:enabled => true,
|
131
|
+
:phrase => "Around hook without 2 parameters for Scenario and Block.",
|
132
|
+
:score => ERROR,
|
133
|
+
:targets => ["Hook"],
|
134
|
+
:reason => "object.type == \"Around\" and object.parameters.count != 2"
|
135
|
+
},
|
136
|
+
:around_hook_no_block_call => {
|
137
|
+
:enabled => true,
|
138
|
+
:phrase => "Around hook does not call its block.",
|
139
|
+
:score => ERROR,
|
140
|
+
:targets => ["Hook"],
|
141
|
+
:reason => "flag = true
|
142
|
+
flag = false if object.type != 'Around'
|
143
|
+
block_call = \"\#{object.parameters[1]}.call\"
|
144
|
+
object.code.each do |line|
|
145
|
+
if line.include?(block_call)
|
146
|
+
flag = false
|
147
|
+
break
|
148
|
+
end
|
149
|
+
end
|
150
|
+
flag"
|
151
|
+
},
|
152
|
+
:hook_no_debugging => {
|
153
|
+
:enabled => true,
|
154
|
+
:phrase => "Hook without a begin/rescue. Reduced visibility when debugging.",
|
155
|
+
:score => ERROR,
|
156
|
+
:targets => ["Hook"],
|
157
|
+
:reason => "(object.code.empty? != true and object.code.join.match(/.*begin.*rescue.*/).nil?)"
|
158
|
+
|
159
|
+
},
|
160
|
+
:hook_conflicting_tags => {
|
161
|
+
:enabled => true,
|
162
|
+
:phrase => "Hook that both expects and ignores the same tag. This hook will not function as expected.",
|
163
|
+
:score => ERROR,
|
164
|
+
:targets => ["Hook"],
|
165
|
+
:reason => "all_tags = []
|
166
|
+
object.tags.each { |single_tag| all_tags << single_tag.split(',') }
|
167
|
+
all_tags.flatten!
|
168
|
+
flag = false
|
169
|
+
all_tags.each do |single_tag|
|
170
|
+
tag = single_tag.gsub(\"~\", \"\")
|
171
|
+
if all_tags.include?(tag) and all_tags.include?(\"~\#{tag}\")
|
172
|
+
flag = true
|
173
|
+
break
|
174
|
+
end
|
175
|
+
end
|
176
|
+
flag
|
177
|
+
"
|
178
|
+
},
|
179
|
+
}
|
180
|
+
|
181
|
+
warning_rules = {
|
182
|
+
:numbers_in_description => {
|
183
|
+
:enabled => true,
|
184
|
+
:phrase => "{class} has numbers in the description.",
|
185
|
+
:score => WARNING,
|
186
|
+
:targets => ["Feature", "Scenario", "Background"],
|
187
|
+
:reason => "!(object.name =~ /\\d+/).nil?"
|
188
|
+
},
|
189
|
+
:empty_feature => {
|
190
|
+
:enabled => true,
|
191
|
+
:phrase => "Feature file has no content.",
|
192
|
+
:score => WARNING,
|
193
|
+
:targets => ["Feature"],
|
194
|
+
:reason => "object.feature_lines == []"
|
195
|
+
},
|
196
|
+
:background_with_no_scenarios => {
|
197
|
+
:enabled => true,
|
198
|
+
:phrase => "Feature has a background with no scenarios.",
|
199
|
+
:score => WARNING,
|
200
|
+
:targets => ["Feature"],
|
201
|
+
:reason => "object.scenarios.empty? and !object.background.nil?"
|
202
|
+
},
|
203
|
+
:background_with_one_scenario => {
|
204
|
+
:enabled => true,
|
205
|
+
:phrase => "Feature has a background with one scenario.",
|
206
|
+
:score => WARNING,
|
207
|
+
:targets => ["Feature"],
|
208
|
+
:reason => "object.scenarios.size == 1 and !object.background.nil?"
|
209
|
+
},
|
210
|
+
:too_many_steps => {
|
211
|
+
:enabled => true,
|
212
|
+
:phrase => "{class} with too many steps.",
|
213
|
+
:score => WARNING,
|
214
|
+
:max => 7,
|
215
|
+
:targets => ["Scenario", "Background"],
|
216
|
+
:reason => "object.steps.count > rule.conditions[:max]"
|
217
|
+
},
|
218
|
+
:out_of_order_steps => {
|
219
|
+
:enabled => true,
|
220
|
+
:phrase => "Scenario steps out of Given/When/Then order.",
|
221
|
+
:score => WARNING,
|
222
|
+
:targets => ["Scenario"],
|
223
|
+
:reason => 'step_order = object.get_step_order
|
224
|
+
["But", "*", "And"].each { |type| step_order.delete(type) }
|
225
|
+
if(step_order != %w(Given When Then) and step_order != %w(When Then))
|
226
|
+
store_rule(object, rule)
|
227
|
+
end'
|
228
|
+
|
229
|
+
},
|
230
|
+
:invalid_first_step => {
|
231
|
+
:enabled => true,
|
232
|
+
:phrase => "Invalid first step. Began with And/But.",
|
233
|
+
:score => WARNING,
|
234
|
+
:targets => ["Scenario", "Background"],
|
235
|
+
:reason => "!(object.steps.first =~ /^\\s*(And|But).*$/).nil?"
|
236
|
+
},
|
237
|
+
:asterisk_step => {
|
238
|
+
:enabled => true,
|
239
|
+
:phrase => "Step includes a * instead of Given/When/Then/And/But.",
|
240
|
+
:score => WARNING,
|
241
|
+
:targets => ["Scenario", "Background"],
|
242
|
+
:reason => "object.steps.each do | step |
|
243
|
+
store_rule(object, rule) if( step =~ /^\\s*[*].*$/)
|
244
|
+
end
|
245
|
+
"
|
246
|
+
},
|
247
|
+
:one_example => {
|
248
|
+
:enabled => true,
|
249
|
+
:phrase => "Scenario Outline with only one example.",
|
250
|
+
:score => WARNING,
|
251
|
+
:targets => ["Scenario"],
|
252
|
+
:reason => "object.type == 'Scenario Outline' and object.examples_table.size == 2 and !is_comment?(object.examples_table[1])"
|
253
|
+
},
|
254
|
+
:too_many_examples => {
|
255
|
+
:enabled => true,
|
256
|
+
:phrase => "Scenario Outline with too many examples.",
|
257
|
+
:score => WARNING,
|
258
|
+
:max => 10,
|
259
|
+
:targets => ["Scenario"],
|
260
|
+
:reason => "object.type == 'Scenario Outline' and (object.examples_table.size - 1) >= rule.conditions[:max]"
|
261
|
+
},
|
262
|
+
:multiple_given_when_then => {
|
263
|
+
:enabled => true,
|
264
|
+
:phrase => "Given/When/Then used multiple times in the same {class}.",
|
265
|
+
:score => WARNING,
|
266
|
+
:targets => ["Scenario", "Background"],
|
267
|
+
:reason => "
|
268
|
+
step_order = object.get_step_order
|
269
|
+
phrase = rule.phrase.gsub('{class}', type)
|
270
|
+
['Given', 'When', 'Then'].each {|step_start| store_rule(object, rule, phrase) if step_order.count(step_start) > 1}"
|
271
|
+
},
|
272
|
+
:too_many_parameters => {
|
273
|
+
:enabled => true,
|
274
|
+
:phrase => "Too many parameters in Step Definition.",
|
275
|
+
:score => WARNING,
|
276
|
+
:max => 4,
|
277
|
+
:targets => ["StepDefinition"],
|
278
|
+
:reason => "object.parameters.size > rule.conditions[:max]"
|
279
|
+
|
280
|
+
},
|
281
|
+
:lazy_debugging => {
|
282
|
+
:enabled => true,
|
283
|
+
:phrase => "Lazy Debugging through puts, p, or print",
|
284
|
+
:score => WARNING,
|
285
|
+
:targets => ["StepDefinition"],
|
286
|
+
:reason => "object.code.each {|line| store_rule(object, rule) if line.strip =~ /^(p|puts)( |\\()('|\\\"|%(q|Q)?\\{)/}"
|
287
|
+
},
|
288
|
+
:pending => {
|
289
|
+
:enabled => true,
|
290
|
+
:phrase => "Pending step definition. Implement or remove.",
|
291
|
+
:score => WARNING,
|
292
|
+
:targets => ["StepDefinition"],
|
293
|
+
:reason => "object.code.each {|line|
|
294
|
+
if line =~ /^\\s*pending(\\(.*\\))?(\\s*[#].*)?$/
|
295
|
+
store_rule(object, rule)
|
296
|
+
break
|
297
|
+
end
|
298
|
+
}"
|
299
|
+
},
|
300
|
+
:feature_same_tag => {
|
301
|
+
:enabled => true,
|
302
|
+
:phrase => "Same tag appears on Feature.",
|
303
|
+
:score => WARNING,
|
304
|
+
:targets => ["Feature"],
|
305
|
+
:reason => 'if(object.scenarios.count >= 2)
|
306
|
+
object.scenarios[1..-1].each do |scenario|
|
307
|
+
object.scenarios.first.tags.each do |tag|
|
308
|
+
store_rule(object, rule) if scenario.tags.include?(tag)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end'
|
312
|
+
},
|
313
|
+
:scenario_same_tag => {
|
314
|
+
:enabled => true,
|
315
|
+
:phrase => "Tag appears on all scenarios.",
|
316
|
+
:score => WARNING,
|
317
|
+
:targets => ["Feature"],
|
318
|
+
#TODO really hacky
|
319
|
+
:reason => "unless object.scenarios.empty?
|
320
|
+
base_tag_list = object.scenarios.first.tags.clone
|
321
|
+
object.scenarios.each do |scenario|
|
322
|
+
base_tag_list.each do |tag|
|
323
|
+
base_tag_list.delete(tag) unless scenario.tags.include?(tag)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
base_tag_list.count.times { store_rule(object, rule) }
|
327
|
+
end"
|
328
|
+
},
|
329
|
+
:commas_in_description => {
|
330
|
+
:enabled => true,
|
331
|
+
:phrase => "There are commas in the description, creating possible multirunning scenarios or features.",
|
332
|
+
:score => WARNING,
|
333
|
+
:targets => ["Feature", "Scenario"],
|
334
|
+
:reason => 'object.name.include?(",")'
|
335
|
+
},
|
336
|
+
:commented_tag => {
|
337
|
+
:enabled => true,
|
338
|
+
:phrase => "{class} has a commented out tag",
|
339
|
+
:score => WARNING,
|
340
|
+
:targets => ["Feature", "Scenario"],
|
341
|
+
:reason => 'object.tags.each do | tag |
|
342
|
+
store_rule(object, rule, rule.phrase.gsub("{class}", type)) if is_comment?(tag)
|
343
|
+
end'
|
344
|
+
},
|
345
|
+
:empty_hook => {
|
346
|
+
:enabled => true,
|
347
|
+
:phrase => "Hook with no content.",
|
348
|
+
:score => WARNING,
|
349
|
+
:targets => ["Hook"],
|
350
|
+
:reason => "object.code == []"
|
351
|
+
},
|
352
|
+
:hook_all_comments => {
|
353
|
+
:enabled => true,
|
354
|
+
:phrase => "Hook is only comments.",
|
355
|
+
:score => WARNING,
|
356
|
+
:targets => ["Hook"],
|
357
|
+
:reason => "flag = true
|
358
|
+
object.code.each do |line|
|
359
|
+
flag = false if line.match(/^\\s*\\#.*$/).nil?
|
360
|
+
end
|
361
|
+
flag"
|
362
|
+
},
|
363
|
+
:hook_duplicate_tags => {
|
364
|
+
:enabled => true,
|
365
|
+
:phrase => "Hook has duplicate tags.",
|
366
|
+
:score => WARNING,
|
367
|
+
:targets => ["Hook"],
|
368
|
+
:reason => "all_tags = []
|
369
|
+
object.tags.each { |single_tag| all_tags << single_tag.split(',') }
|
370
|
+
all_tags.flatten!
|
371
|
+
unique_tags = all_tags.uniq
|
372
|
+
true unless all_tags == unique_tags"
|
373
|
+
}
|
374
|
+
}
|
375
|
+
|
376
|
+
info_rules = {
|
377
|
+
:too_many_tags => {
|
378
|
+
:enabled => true,
|
379
|
+
:phrase => "{class} has too many tags.",
|
380
|
+
:score => INFO,
|
381
|
+
:max => 8,
|
382
|
+
:targets => ["Feature", "Scenario"],
|
383
|
+
:reason => "object.tags.size >= rule.conditions[:max]"
|
384
|
+
},
|
385
|
+
:long_name => {
|
386
|
+
:enabled => true,
|
387
|
+
:phrase => "{class} has a long description.",
|
388
|
+
:score => INFO,
|
389
|
+
:max => 180,
|
390
|
+
:targets => ["Feature", "Scenario", "Background"],
|
391
|
+
:reason => "object.name.length >= rule.conditions[:max]"
|
392
|
+
},
|
393
|
+
:implementation_word => {
|
394
|
+
:enabled => true,
|
395
|
+
:phrase => "Implementation word used: {word}.",
|
396
|
+
:score => INFO,
|
397
|
+
:words => ["page", "site", "url", "drop down", "dropdown", "select list", "click", "text box", "radio button", "check box", "xml", "window", "pop up", "pop-up", "screen", "tab", "database", "DB"],
|
398
|
+
:targets => ["Scenario", "Background"],
|
399
|
+
:reason => "object.steps.each do |step|
|
400
|
+
next if is_comment?(step)
|
401
|
+
rule.conditions[:words].each do |word|
|
402
|
+
new_phrase = rule.phrase.gsub(/{.*}/, word)
|
403
|
+
store_rule(object, rule, new_phrase) if step.include?(word)
|
404
|
+
end
|
405
|
+
end"
|
406
|
+
|
407
|
+
},
|
408
|
+
:implementation_word_button => {
|
409
|
+
:enabled => true,
|
410
|
+
:phrase => "Implementation word used: button.",
|
411
|
+
:score => INFO,
|
412
|
+
:targets => ["Scenario"],
|
413
|
+
:reason => "object.steps.each do |step|
|
414
|
+
matches = step.match(/(?<prefix>\\w+)\\sbutton/i)
|
415
|
+
if(!matches.nil? and matches[:prefix].downcase != 'radio')
|
416
|
+
store_rule(object, rule)
|
417
|
+
end
|
418
|
+
end"
|
419
|
+
|
420
|
+
},:too_many_scenarios => {
|
421
|
+
:enabled => true,
|
422
|
+
:phrase => "Feature with too many scenarios.",
|
423
|
+
:score => INFO,
|
424
|
+
:max => 10,
|
425
|
+
:targets => ["Feature"],
|
426
|
+
:reason => "object.scenarios.size >= rule.conditions[:max]"
|
427
|
+
},
|
428
|
+
:date_used => {
|
429
|
+
:enabled => true,
|
430
|
+
:phrase => "Date used.",
|
431
|
+
:score => INFO,
|
432
|
+
:targets => ["Scenario", "Background"],
|
433
|
+
:reason => "object.steps.each {|step| store_rule(object, rule) if step =~ DATE_REGEX}"
|
434
|
+
},
|
435
|
+
:nested_step => {
|
436
|
+
:enabled => true,
|
437
|
+
:phrase => "Nested step call.",
|
438
|
+
:score => INFO,
|
439
|
+
:targets => ["StepDefinition"],
|
440
|
+
:reason => "!object.nested_steps.empty?"
|
441
|
+
},
|
442
|
+
:commented_code => {
|
443
|
+
:enabled => true,
|
444
|
+
:phrase => "Commented code in Step Definition.",
|
445
|
+
:score => INFO,
|
446
|
+
:targets => ["StepDefinition"],
|
447
|
+
:reason => "object.code.each {|line| store_rule(object, rule) if is_comment?(line)}"
|
448
|
+
},
|
449
|
+
:small_sleep => {
|
450
|
+
:enabled => true,
|
451
|
+
:phrase => "Small sleeps used. Use a wait_until like method.",
|
452
|
+
:score => INFO,
|
453
|
+
:max => 2,
|
454
|
+
:targets => ["StepDefinition"],
|
455
|
+
:reason => "object.code.each do |line|
|
456
|
+
match_data = line.match /^\\s*sleep(\\s|\\()(?<sleep_time>.*)\\)?/
|
457
|
+
if match_data
|
458
|
+
sleep_value = match_data[:sleep_time].to_f
|
459
|
+
store_rule(object, rule) if sleep_value < rule.conditions[:max]
|
460
|
+
end
|
461
|
+
end"
|
462
|
+
},
|
463
|
+
:large_sleep => {
|
464
|
+
:enabled => true,
|
465
|
+
:phrase => "Large sleeps used. Use a wait_until like method.",
|
466
|
+
:score => INFO,
|
467
|
+
:min => 2,
|
468
|
+
:targets => ["StepDefinition"],
|
469
|
+
:reason => "object.code.each do |line|
|
470
|
+
match_data = line.match /^\\s*sleep(\\s|\\()(?<sleep_time>.*)\\)?/
|
471
|
+
if match_data
|
472
|
+
sleep_value = match_data[:sleep_time].to_f
|
473
|
+
store_rule(object, rule) if sleep_value > rule.conditions[:min]
|
474
|
+
end
|
475
|
+
end"
|
476
|
+
},
|
477
|
+
:todo => {
|
478
|
+
:enabled => true,
|
479
|
+
:phrase => "Todo found. Resolve it.",
|
480
|
+
:score => INFO,
|
481
|
+
:targets => ["StepDefinition"],
|
482
|
+
:reason => "object.code.each {|line| store_rule(object, rule) if line =~ /\\#(TODO|todo)/}
|
483
|
+
false"
|
484
|
+
},
|
485
|
+
:hook_not_in_hooks_file => {
|
486
|
+
:enabled => true,
|
487
|
+
:phrase => "Hook found outside of the designated hooks file",
|
488
|
+
:score => INFO,
|
489
|
+
:file => "hooks.rb",
|
490
|
+
:targets => ["Hook"],
|
491
|
+
:reason => "object.location.include?(rule.conditions[:file]) != true"
|
492
|
+
},
|
493
|
+
}
|
494
|
+
|
495
|
+
# Master hash used for rule data
|
496
|
+
# * +:enabled+
|
497
|
+
# * +:phrase+
|
498
|
+
# * +:score+
|
499
|
+
# * +:targets+
|
500
|
+
# * +:reason+
|
501
|
+
# Optional:
|
502
|
+
# * +:words+
|
503
|
+
# * +:max+
|
504
|
+
# * +:min+
|
505
|
+
# * +:file+
|
506
|
+
RULES = {}.merge fatal_rules.merge error_rules.merge warning_rules.merge info_rules
|
507
|
+
end
|
508
|
+
end
|