cuke_sniffer 0.0.5 → 0.0.6

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.
Files changed (31) hide show
  1. data/bin/cuke_sniffer +85 -63
  2. data/lib/cuke_sniffer.rb +23 -16
  3. data/lib/cuke_sniffer/cli.rb +269 -523
  4. data/lib/cuke_sniffer/constants.rb +5 -2
  5. data/lib/cuke_sniffer/cuke_sniffer_helper.rb +165 -0
  6. data/lib/cuke_sniffer/dead_steps_helper.rb +54 -0
  7. data/lib/cuke_sniffer/feature.rb +13 -81
  8. data/lib/cuke_sniffer/feature_rules_evaluator.rb +56 -115
  9. data/lib/cuke_sniffer/formatter.rb +142 -0
  10. data/lib/cuke_sniffer/hook.rb +77 -160
  11. data/lib/cuke_sniffer/report/dead_steps.html.erb +36 -0
  12. data/lib/cuke_sniffer/report/features.html.erb +60 -0
  13. data/lib/cuke_sniffer/report/hooks.html.erb +49 -0
  14. data/lib/cuke_sniffer/report/improvement_list.html.erb +18 -0
  15. data/lib/cuke_sniffer/report/legend.html.erb +167 -0
  16. data/lib/cuke_sniffer/report/min_template.html.erb +125 -0
  17. data/lib/cuke_sniffer/report/rules.html.erb +9 -0
  18. data/lib/cuke_sniffer/report/standard_template.html.erb +137 -0
  19. data/lib/cuke_sniffer/report/step_definitions.html.erb +49 -0
  20. data/lib/cuke_sniffer/report/sub_rules.html.erb +24 -0
  21. data/lib/cuke_sniffer/report/summary.html.erb +71 -0
  22. data/lib/cuke_sniffer/rule.rb +28 -0
  23. data/lib/cuke_sniffer/rule_config.rb +241 -48
  24. data/lib/cuke_sniffer/rule_target.rb +62 -0
  25. data/lib/cuke_sniffer/rules_evaluator.rb +65 -70
  26. data/lib/cuke_sniffer/scenario.rb +102 -238
  27. data/lib/cuke_sniffer/step_definition.rb +163 -239
  28. data/lib/cuke_sniffer/summary_helper.rb +91 -0
  29. data/lib/cuke_sniffer/summary_node.rb +19 -0
  30. metadata +23 -5
  31. data/lib/cuke_sniffer/report/markup.rhtml +0 -353
@@ -0,0 +1,49 @@
1
+ <div class="title" onclick="toggleById('step_definitions_data', this)">
2
+ Step Definitions +
3
+ </div>
4
+ <div class="shrink_section" id="step_definitions_data">
5
+ <% if cuke_sniffer.step_definitions.count == 0 %>
6
+ <div class="empty_set_message">There were no Step Definitions to sniff in '<%= cuke_sniffer.step_definitions_location %>'!</div>
7
+ <% elsif cuke_sniffer.step_definitions.count >= 1 && cuke_sniffer.summary[:step_definitions][:total_score] ==0 %>
8
+ <div class="empty_set_message">Excellent! No smells found for Step Definitions!</div>
9
+ <% end %>
10
+ <% index = 0 %>
11
+ <% cuke_sniffer.step_definitions.each do |step_definition| %>
12
+ <% next if step_definition.score <= 0 %>
13
+ <div style="cursor: pointer; padding-top: 3px" onclick="updateDisplayStatus(document.getElementById('step_definition_<%=index%>'))">
14
+ <div class="red_number" style="float:left"><%= step_definition.score %></div>
15
+ <% parameters = "" %>
16
+ <% step_definition.parameters.each do |param| %>
17
+ <% if param == step_definition.parameters.first %>
18
+ <% parameters << "| " %>
19
+ <% end %>
20
+ <% parameters << param %>
21
+
22
+ <% if param == step_definition.parameters.last %>
23
+ <% parameters << " |" %>
24
+ <% else %>
25
+ <% parameters << ", " %>
26
+ <% end %>
27
+ <% end %>
28
+ <div style="float: left" class="notes"><%= "/" + step_definition.regex.to_s.match(/\(\?\-mix\:(?<regex>.*)\)/)[:regex] + "/ do " + parameters %></div>
29
+ <div style="float: right; padding-right:1%" class="notes"><%= step_definition.calls.keys.count %> Call(s)</div>
30
+ </div>
31
+ <div id="step_definition_<%= index %>" style="display:none;">
32
+ <br style="clear:both">
33
+
34
+ <div class="sub_section" style="display:block">
35
+ <div style="text-indent: 4%; float: left; width: 45%; border-right: 2px solid #add8e6;">
36
+ <% step_definition.rules_hash.each_key do |rule| %>
37
+ <pre style="margin: 0;"><%= rule %></pre>
38
+ <% end %>
39
+ </div>
40
+ <div style="text-indent: 1%">
41
+ <p style="margin:0"><%= step_definition.location.gsub(cuke_sniffer.step_definitions_location, "") %></p>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ <br style="clear:both">
46
+ <% index += 1 %>
47
+ <% end %>
48
+ </div>
49
+ <br style="clear:both">
@@ -0,0 +1,24 @@
1
+ <ol class="rule_style">
2
+ <p class="rule_heading"><%= heading %></p>
3
+ <% cuke_sniffer.rules.select { |rule| rule.enabled == state }.each do |rule| %>
4
+ <li class="rule_notes">
5
+ <div class="rule_details">Phrase: <%= rule.phrase %></div>
6
+ <div class="rule_details">Score: <%= rule.score %></div>
7
+ <% rule.conditions.each do |key, value| %>
8
+ <% if value.kind_of?(Array) %>
9
+ <div class="rule_details"><%= key.capitalize %>:</div>
10
+
11
+ <% result = CukeSniffer::Formatter.convert_array_condition_into_list_of_strings(value) %>
12
+ <% result.first.prepend("[") %>
13
+ <% result.last << "]" %>
14
+ <% result.each do |words| %>
15
+ <div><%= words %></div>
16
+ <% end %>
17
+
18
+ <% else %>
19
+ <div class="rule_details"><%= key.capitalize %>: <%= value %></div>
20
+ <% end %>
21
+ </li>
22
+ <% end %>
23
+ <% end %>
24
+ </ol>
@@ -0,0 +1,71 @@
1
+ <div class="title" onclick="toggleById('summary_data', this)">
2
+ Summary -
3
+ </div>
4
+ <div class="shrink_section" style="display: block;" id="summary_data">
5
+ <% data_symbols = [:features, :scenarios, :step_definitions, :hooks] %>
6
+ <table style="width: 100%;" class="notes">
7
+ <tr class="divide_row">
8
+ <td class="table_top"></td>
9
+ <td class="table_top">Features</td>
10
+ <td class="table_top">Scenarios</td>
11
+ <td class="table_top">Step Definitions</td>
12
+ <td class="table_top">Hooks</td>
13
+ </tr>
14
+ <tr>
15
+ <td>Total Score</td>
16
+ <% data_symbols.each do |symbol| %>
17
+ <td><%= cuke_sniffer.summary[symbol][:total_score] %></td>
18
+ <% end %>
19
+ </tr>
20
+ <tr>
21
+ <td>Count</td>
22
+ <% data_symbols.each do |symbol| %>
23
+ <td><%= cuke_sniffer.summary[symbol][:total].inspect %></td>
24
+ <% end %>
25
+ </tr>
26
+ <tr/>
27
+ <tr>
28
+ <td>Lowest Score</td>
29
+ <% data_symbols.each do |symbol| %>
30
+ <td><%= cuke_sniffer.summary[symbol][:min] %></td>
31
+ <% end %>
32
+ </tr>
33
+ <tr>
34
+ <td>Highest Score</td>
35
+ <% data_symbols.each do |symbol| %>
36
+ <td><%= cuke_sniffer.summary[symbol][:max] %></td>
37
+ <% end %>
38
+ </tr>
39
+ <tr>
40
+ <td>Average Score</td>
41
+ <% data_symbols.each do |symbol| %>
42
+ <td><%= cuke_sniffer.summary[symbol][:average] %></td>
43
+ <% end %>
44
+ </tr>
45
+ <tr/>
46
+
47
+ <tr>
48
+ <td>Threshold</td>
49
+ <% data_symbols.each do |symbol| %>
50
+ <td><%= cuke_sniffer.summary[symbol][:threshold] %></td>
51
+ <% end %>
52
+ </tr>
53
+ <tr>
54
+ <td>Good</td>
55
+ <% data_symbols.each do |symbol| %>
56
+ <td><%= ((cuke_sniffer.summary[symbol][:good].to_f/cuke_sniffer.summary[symbol][:total].to_f) * 100).round(2) %>
57
+ %
58
+ </td>
59
+ <% end %>
60
+ </tr>
61
+ <tr>
62
+ <td>Bad</td>
63
+ <% data_symbols.each do |symbol| %>
64
+ <td><%= ((cuke_sniffer.summary[symbol][:bad].to_f/cuke_sniffer.summary[symbol][:total].to_f) * 100).round(2) %>
65
+ %
66
+ </td>
67
+ <% end %>
68
+ </tr>
69
+ </table>
70
+ </div>
71
+ <br style="clear:both">
@@ -0,0 +1,28 @@
1
+ require 'roxml'
2
+ module CukeSniffer
3
+
4
+ # Author:: Robert Cochran (mailto:cochrarj@miamioh.edu)
5
+ # Copyright:: Copyright (C) 2013 Robert Cochran
6
+ # License:: Distributes under the MIT License
7
+ # Single Rule object used for passing around rule data and serializing out to xml
8
+ # Mixins: ROXML
9
+ class Rule
10
+ include ROXML
11
+ xml_accessor :enabled
12
+ xml_accessor :phrase
13
+ xml_accessor :score
14
+ xml_accessor :conditions, :as => {:key => "name", :value => "value"}, :in => "conditions", :from => "condition"
15
+ xml_accessor :targets, :in => "targets"
16
+ xml_accessor :reason
17
+
18
+ def initialize
19
+ @enabled = false
20
+ @phrase = ""
21
+ @score = 0
22
+ @conditions = {}
23
+ @targets = []
24
+ @reason = ""
25
+ end
26
+ end
27
+
28
+ end
@@ -19,27 +19,46 @@ module CukeSniffer
19
19
  :no_examples => {
20
20
  :enabled => true,
21
21
  :phrase => "Scenario Outline with no examples.",
22
- :score => FATAL
22
+ :score => FATAL,
23
+ :targets => ["Scenario"],
24
+ :reason => "object.type == \"Scenario Outline\" and object.examples_table.size == 1"
23
25
  },
24
26
  :no_examples_table => {
25
27
  :enabled => true,
26
28
  :phrase => "Scenario Outline with no examples table.",
27
- :score => FATAL
29
+ :score => FATAL,
30
+ :targets => ["Scenario"],
31
+ :reason => "object.type == \"Scenario Outline\" and object.examples_table.empty?"
28
32
  },
29
33
  :recursive_nested_step => {
30
34
  :enabled => true,
31
35
  :phrase => "Recursive nested step call.",
32
- :score => FATAL
36
+ :score => FATAL,
37
+ :targets => ["StepDefinition"],
38
+ :reason => "object.nested_steps.each_value {|nested_step| store_rule(object, rule) if nested_step =~ object.regex}"
33
39
  },
34
40
  :background_with_tag => {
35
41
  :enabled => true,
36
42
  :phrase => "There is a background with a tag. This feature file cannot run!",
37
- :score => FATAL
43
+ :score => FATAL,
44
+ :targets => ["Background"],
45
+ :reason => "object.tags.size > 0"
38
46
  },
39
47
  :comment_after_tag => {
40
48
  :enabled => true,
41
49
  :phrase => "Comment comes between tag and properly executing line. This feature file cannot run!",
42
- :score => FATAL
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"
43
62
  }
44
63
  }
45
64
 
@@ -47,57 +66,105 @@ module CukeSniffer
47
66
  :no_description => {
48
67
  :enabled => true,
49
68
  :phrase => "{class} has no description.",
50
- :score => ERROR
69
+ :score => ERROR,
70
+ :targets => ["Feature", "Scenario"],
71
+ :reason => "object.name.empty?"
51
72
  },
52
73
  :no_scenarios => {
53
74
  :enabled => true,
54
75
  :phrase => "Feature with no scenarios.",
55
- :score => ERROR
76
+ :score => ERROR,
77
+ :targets => ["Feature"],
78
+ :reason => "object.scenarios.empty?"
56
79
  },
57
80
  :commented_step => {
58
81
  :enabled => true,
59
82
  :phrase => "Commented step.",
60
- :score => ERROR
83
+ :score => ERROR,
84
+ :targets => ["Scenario", "Background"],
85
+ :reason => "object.steps.each do |step|
86
+ store_rule(object, rule) if is_comment?(step)
87
+ end"
61
88
  },
62
89
  :commented_example => {
63
90
  :enabled => true,
64
91
  :phrase => "Commented example.",
65
- :score => ERROR
92
+ :score => ERROR,
93
+ :targets => ["Scenario"],
94
+ :reason => "if object.type == 'Scenario Outline'
95
+ object.examples_table.each {|example| store_rule(object, rule) if is_comment?(example)}
96
+ end"
66
97
  },
67
98
  :no_steps => {
68
99
  :enabled => true,
69
100
  :phrase => "No steps in Scenario.",
70
- :score => ERROR
101
+ :score => ERROR,
102
+ :targets => ["Scenario", "Background"],
103
+ :reason => "object.steps.empty?"
71
104
  },
72
105
  :one_word_step => {
73
106
  :enabled => true,
74
107
  :phrase => "Step that is only one word long.",
75
- :score => ERROR
108
+ :score => ERROR,
109
+ :targets => ["Scenario", "Background"],
110
+ :reason => "object.steps.each {|step| store_rule(object, rule) if step.split.count == 2}"
76
111
  },
77
112
  :no_code => {
78
113
  :enabled => true,
79
114
  :phrase => "No code in Step Definition.",
80
- :score => ERROR
115
+ :score => ERROR,
116
+ :targets => ["StepDefinition"],
117
+ :reason => "object.code.empty?"
81
118
  },
82
119
  :around_hook_without_2_parameters => {
83
120
  :enabled => true,
84
121
  :phrase => "Around hook without 2 parameters for Scenario and Block.",
85
- :score => ERROR
122
+ :score => ERROR,
123
+ :targets => ["Hook"],
124
+ :reason => "object.type == \"Around\" and object.parameters.count != 2"
86
125
  },
87
126
  :around_hook_no_block_call => {
88
127
  :enabled => true,
89
128
  :phrase => "Around hook does not call its block.",
90
- :score => ERROR
129
+ :score => ERROR,
130
+ :targets => ["Hook"],
131
+ :reason => "flag = true
132
+ flag = false if object.type != 'Around'
133
+ block_call = \"\#{object.parameters[1]}.call\"
134
+ object.code.each do |line|
135
+ if line.include?(block_call)
136
+ flag = false
137
+ break
138
+ end
139
+ end
140
+ flag"
91
141
  },
92
142
  :hook_no_debugging => {
93
143
  :enabled => true,
94
144
  :phrase => "Hook without a begin/rescue. Reduced visibility when debugging.",
95
- :score => ERROR
145
+ :score => ERROR,
146
+ :targets => ["Hook"],
147
+ :reason => "(object.code.empty? != true and object.code.join.match(/.*begin.*rescue.*/).nil?)"
148
+
96
149
  },
97
150
  :hook_conflicting_tags => {
98
151
  :enabled => true,
99
152
  :phrase => "Hook that both expects and ignores the same tag. This hook will not function as expected.",
100
- :score => ERROR
153
+ :score => ERROR,
154
+ :targets => ["Hook"],
155
+ :reason => "all_tags = []
156
+ object.tags.each { |single_tag| all_tags << single_tag.split(',') }
157
+ all_tags.flatten!
158
+ flag = false
159
+ all_tags.each do |single_tag|
160
+ tag = single_tag.gsub(\"~\", \"\")
161
+ if all_tags.include?(tag) and all_tags.include?(\"~\#{tag}\")
162
+ flag = true
163
+ break
164
+ end
165
+ end
166
+ flag
167
+ "
101
168
  },
102
169
  }
103
170
 
@@ -105,110 +172,194 @@ module CukeSniffer
105
172
  :numbers_in_description => {
106
173
  :enabled => true,
107
174
  :phrase => "{class} has numbers in the description.",
108
- :score => WARNING
175
+ :score => WARNING,
176
+ :targets => ["Feature", "Scenario", "Background"],
177
+ :reason => "!(object.name =~ /\\d+/).nil?"
109
178
  },
110
179
  :empty_feature => {
111
180
  :enabled => true,
112
181
  :phrase => "Feature file has no content.",
113
- :score => WARNING
182
+ :score => WARNING,
183
+ :targets => ["Feature"],
184
+ :reason => "object.feature_lines == []"
114
185
  },
115
186
  :background_with_no_scenarios => {
116
187
  :enabled => true,
117
188
  :phrase => "Feature has a background with no scenarios.",
118
- :score => WARNING
189
+ :score => WARNING,
190
+ :targets => ["Feature"],
191
+ :reason => "object.scenarios.empty? and !object.background.nil?"
119
192
  },
120
193
  :background_with_one_scenario => {
121
194
  :enabled => true,
122
195
  :phrase => "Feature has a background with one scenario.",
123
- :score => WARNING
196
+ :score => WARNING,
197
+ :targets => ["Feature"],
198
+ :reason => "object.scenarios.size == 1 and !object.background.nil?"
124
199
  },
125
200
  :too_many_steps => {
126
201
  :enabled => true,
127
202
  :phrase => "{class} with too many steps.",
128
203
  :score => WARNING,
129
- :max => 7
204
+ :max => 7,
205
+ :targets => ["Scenario", "Background"],
206
+ :reason => "object.steps.count > rule.conditions[:max]"
130
207
  },
131
208
  :out_of_order_steps => {
132
209
  :enabled => true,
133
210
  :phrase => "Scenario steps out of Given/When/Then order.",
134
- :score => WARNING
211
+ :score => WARNING,
212
+ :targets => ["Scenario"],
213
+ :reason => 'step_order = object.get_step_order
214
+ ["But", "*", "And"].each { |type| step_order.delete(type) }
215
+ if(step_order != %w(Given When Then) and step_order != %w(When Then))
216
+ store_rule(object, rule)
217
+ end'
218
+
135
219
  },
136
220
  :invalid_first_step => {
137
221
  :enabled => true,
138
222
  :phrase => "Invalid first step. Began with And/But.",
139
- :score => WARNING
223
+ :score => WARNING,
224
+ :targets => ["Scenario", "Background"],
225
+ :reason => "!(object.steps.first =~ /^\\s*(And|But).*$/).nil?"
140
226
  },
141
227
  :asterisk_step => {
142
228
  :enabled => true,
143
229
  :phrase => "Step includes a * instead of Given/When/Then/And/But.",
144
- :score => WARNING
230
+ :score => WARNING,
231
+ :targets => ["Scenario", "Background"],
232
+ :reason => "object.steps.each do | step |
233
+ store_rule(object, rule) if( step =~ /^\\s*[*].*$/)
234
+ end
235
+ "
145
236
  },
146
237
  :one_example => {
147
238
  :enabled => true,
148
239
  :phrase => "Scenario Outline with only one example.",
149
- :score => WARNING
240
+ :score => WARNING,
241
+ :targets => ["Scenario"],
242
+ :reason => "object.type == 'Scenario Outline' and object.examples_table.size == 2 and !is_comment?(object.examples_table[1])"
150
243
  },
151
244
  :too_many_examples => {
152
245
  :enabled => true,
153
246
  :phrase => "Scenario Outline with too many examples.",
154
247
  :score => WARNING,
155
- :max => 10
248
+ :max => 10,
249
+ :targets => ["Scenario"],
250
+ :reason => "object.type == 'Scenario Outline' and (object.examples_table.size - 1) >= rule.conditions[:max]"
156
251
  },
157
252
  :multiple_given_when_then => {
158
253
  :enabled => true,
159
254
  :phrase => "Given/When/Then used multiple times in the same {class}.",
160
- :score => WARNING
255
+ :score => WARNING,
256
+ :targets => ["Scenario", "Background"],
257
+ :reason => "
258
+ step_order = object.get_step_order
259
+ phrase = rule.phrase.gsub('{class}', type)
260
+ ['Given', 'When', 'Then'].each {|step_start| store_rule(object, rule, phrase) if step_order.count(step_start) > 1}"
161
261
  },
162
262
  :too_many_parameters => {
163
263
  :enabled => true,
164
264
  :phrase => "Too many parameters in Step Definition.",
165
265
  :score => WARNING,
166
- :max => 4
266
+ :max => 4,
267
+ :targets => ["StepDefinition"],
268
+ :reason => "object.parameters.size > rule.conditions[:max]"
269
+
167
270
  },
168
271
  :lazy_debugging => {
169
272
  :enabled => true,
170
273
  :phrase => "Lazy Debugging through puts, p, or print",
171
- :score => WARNING
274
+ :score => WARNING,
275
+ :targets => ["StepDefinition"],
276
+ :reason => "object.code.each {|line| store_rule(object, rule) if line.strip =~ /^(p|puts)( |\\()('|\\\"|%(q|Q)?\\{)/}"
172
277
  },
173
278
  :pending => {
174
279
  :enabled => true,
175
280
  :phrase => "Pending step definition. Implement or remove.",
176
- :score => WARNING
281
+ :score => WARNING,
282
+ :targets => ["StepDefinition"],
283
+ :reason => "object.code.each {|line|
284
+ if line =~ /^\\s*pending(\\(.*\\))?(\\s*[#].*)?$/
285
+ store_rule(object, rule)
286
+ break
287
+ end
288
+ }"
177
289
  },
178
290
  :feature_same_tag => {
179
291
  :enabled => true,
180
292
  :phrase => "Same tag appears on Feature.",
181
- :score => WARNING
293
+ :score => WARNING,
294
+ :targets => ["Feature"],
295
+ :reason => 'if(object.scenarios.count >= 2)
296
+ object.scenarios[1..-1].each do |scenario|
297
+ object.scenarios.first.tags.each do |tag|
298
+ store_rule(object, rule) if scenario.tags.include?(tag)
299
+ end
300
+ end
301
+ end'
182
302
  },
183
303
  :scenario_same_tag => {
184
304
  :enabled => true,
185
305
  :phrase => "Tag appears on all scenarios.",
186
- :score => WARNING
306
+ :score => WARNING,
307
+ :targets => ["Feature"],
308
+ #TODO really hacky
309
+ :reason => "unless object.scenarios.empty?
310
+ base_tag_list = object.scenarios.first.tags.clone
311
+ object.scenarios.each do |scenario|
312
+ base_tag_list.each do |tag|
313
+ base_tag_list.delete(tag) unless scenario.tags.include?(tag)
314
+ end
315
+ end
316
+ base_tag_list.count.times { store_rule(object, rule) }
317
+ end"
187
318
  },
188
319
  :commas_in_description => {
189
320
  :enabled => true,
190
321
  :phrase => "There are commas in the description, creating possible multirunning scenarios or features.",
191
- :score => WARNING
322
+ :score => WARNING,
323
+ :targets => ["Feature", "Scenario"],
324
+ :reason => 'object.name.include?(",")'
192
325
  },
193
326
  :commented_tag => {
194
327
  :enabled => true,
195
328
  :phrase => "{class} has a commented out tag",
196
- :score => WARNING
329
+ :score => WARNING,
330
+ :targets => ["Feature", "Scenario"],
331
+ :reason => 'object.tags.each do | tag |
332
+ store_rule(object, rule, rule.phrase.gsub("{class}", type)) if is_comment?(tag)
333
+ end'
197
334
  },
198
335
  :empty_hook => {
199
336
  :enabled => true,
200
337
  :phrase => "Hook with no content.",
201
- :score => WARNING
338
+ :score => WARNING,
339
+ :targets => ["Hook"],
340
+ :reason => "object.code == []"
202
341
  },
203
342
  :hook_all_comments => {
204
343
  :enabled => true,
205
344
  :phrase => "Hook is only comments.",
206
- :score => WARNING
345
+ :score => WARNING,
346
+ :targets => ["Hook"],
347
+ :reason => "flag = true
348
+ object.code.each do |line|
349
+ flag = false if line.match(/^\\s*\\#.*$/).nil?
350
+ end
351
+ flag"
207
352
  },
208
353
  :hook_duplicate_tags => {
209
354
  :enabled => true,
210
355
  :phrase => "Hook has duplicate tags.",
211
- :score => WARNING
356
+ :score => WARNING,
357
+ :targets => ["Hook"],
358
+ :reason => "all_tags = []
359
+ object.tags.each { |single_tag| all_tags << single_tag.split(',') }
360
+ all_tags.flatten!
361
+ unique_tags = all_tags.uniq
362
+ true unless all_tags == unique_tags"
212
363
  }
213
364
  }
214
365
 
@@ -217,63 +368,105 @@ module CukeSniffer
217
368
  :enabled => true,
218
369
  :phrase => "{class} has too many tags.",
219
370
  :score => INFO,
220
- :max => 8
371
+ :max => 8,
372
+ :targets => ["Feature", "Scenario"],
373
+ :reason => "object.tags.size >= rule.conditions[:max]"
221
374
  },
222
375
  :long_name => {
223
376
  :enabled => true,
224
377
  :phrase => "{class} has a long description.",
225
378
  :score => INFO,
226
- :max => 180
379
+ :max => 180,
380
+ :targets => ["Feature", "Scenario", "Background"],
381
+ :reason => "object.name.length >= rule.conditions[:max]"
227
382
  },
228
383
  :implementation_word => {
229
384
  :enabled => true,
230
385
  :phrase => "Implementation word used: {word}.",
231
386
  :score => INFO,
232
- :words => ["page", "site", "url", "button", "drop down", "dropdown", "select list", "click", "text box", "radio button", "check box", "xml", "window", "pop up", "pop-up", "screen"]
387
+ :words => ["page", "site", "url", "button", "drop down", "dropdown", "select list", "click", "text box", "radio button", "check box", "xml", "window", "pop up", "pop-up", "screen"],
388
+ :targets => ["Scenario", "Background"],
389
+ :reason => "object.steps.each do |step|
390
+ next if is_comment?(step)
391
+ rule.conditions[:words].each do |word|
392
+ new_phrase = rule.phrase.gsub(/{.*}/, word)
393
+ store_rule(object, rule, new_phrase) if step.include?(word)
394
+ end
395
+ end"
396
+
233
397
  },
234
398
  :too_many_scenarios => {
235
399
  :enabled => true,
236
400
  :phrase => "Feature with too many scenarios.",
237
401
  :score => INFO,
238
- :max => 10
402
+ :max => 10,
403
+ :targets => ["Feature"],
404
+ :reason => "object.scenarios.size >= rule.conditions[:max]"
239
405
  },
240
406
  :date_used => {
241
407
  :enabled => true,
242
408
  :phrase => "Date used.",
243
- :score => INFO
409
+ :score => INFO,
410
+ :targets => ["Scenario", "Background"],
411
+ :reason => "object.steps.each {|step| store_rule(object, rule) if step =~ DATE_REGEX}"
244
412
  },
245
413
  :nested_step => {
246
414
  :enabled => true,
247
415
  :phrase => "Nested step call.",
248
- :score => INFO
416
+ :score => INFO,
417
+ :targets => ["StepDefinition"],
418
+ :reason => "!object.nested_steps.empty?"
249
419
  },
250
420
  :commented_code => {
251
421
  :enabled => true,
252
422
  :phrase => "Commented code in Step Definition.",
253
- :score => INFO
423
+ :score => INFO,
424
+ :targets => ["StepDefinition"],
425
+ :reason => "object.code.each {|line| store_rule(object, rule) if is_comment?(line)}"
254
426
  },
255
427
  :small_sleep => {
256
428
  :enabled => true,
257
429
  :phrase => "Small sleeps used. Use a wait_until like method.",
258
430
  :score => INFO,
259
- :max => 2
431
+ :max => 2,
432
+ :targets => ["StepDefinition"],
433
+ :reason => "object.code.each do |line|
434
+ match_data = line.match /^\\s*sleep(\\s|\\()(?<sleep_time>.*)\\)?/
435
+ if match_data
436
+ sleep_value = match_data[:sleep_time].to_f
437
+ store_rule(object, rule) if sleep_value < rule.conditions[:max]
438
+ end
439
+ end"
260
440
  },
261
441
  :large_sleep => {
262
442
  :enabled => true,
263
443
  :phrase => "Large sleeps used. Use a wait_until like method.",
264
444
  :score => INFO,
265
- :min => 2
445
+ :min => 2,
446
+ :targets => ["StepDefinition"],
447
+ :reason => "object.code.each do |line|
448
+ match_data = line.match /^\\s*sleep(\\s|\\()(?<sleep_time>.*)\\)?/
449
+ if match_data
450
+ sleep_value = match_data[:sleep_time].to_f
451
+ store_rule(object, rule) if sleep_value > rule.conditions[:min]
452
+ end
453
+ end"
266
454
  },
267
455
  :todo => {
268
456
  :enabled => true,
269
457
  :phrase => "Todo found. Resolve it.",
270
- :score => INFO
458
+ :score => INFO,
459
+ :targets => ["StepDefinition"],
460
+ :reason => "object.code.each {|line| store_rule(object, rule) if line =~ /\\#(TODO|todo)/}
461
+ false"
271
462
  },
272
463
  :hook_not_in_hooks_file => {
273
464
  :enabled => true,
274
465
  :phrase => "Hook found outside of the designated hooks file",
275
466
  :score => INFO,
276
- :file => "hooks.rb"
467
+ :file => "hooks.rb",
468
+ :targets => ["Hook"],
469
+ :reason => "object.location.include?(rule.conditions[:file]) != true"
277
470
  },
278
471
  }
279
472