tailor 0.0.3 → 0.1.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.
Files changed (44) hide show
  1. data/History.txt +8 -0
  2. data/Manifest.txt +22 -2
  3. data/PostInstall.txt +4 -0
  4. data/README.rdoc +7 -6
  5. data/Rakefile +7 -1
  6. data/bin/tailor +21 -5
  7. data/features/indentation.feature +22 -0
  8. data/features/spacing.feature +13 -18
  9. data/features/spacing/commas.feature +44 -0
  10. data/features/step_definitions/indentation_steps.rb +139 -0
  11. data/features/step_definitions/spacing/commas_steps.rb +14 -0
  12. data/features/step_definitions/spacing_steps.rb +1 -36
  13. data/features/support/1_file_with_bad_comma_spacing/bad_comma_spacing.rb +43 -5
  14. data/features/support/1_file_with_bad_curly_brace_spacing/bad_curly_brace_spacing.rb +60 -0
  15. data/features/support/1_file_with_bad_operator_spacing/bad_op_spacing.rb +31 -0
  16. data/features/support/1_file_with_bad_parenthesis/bad_parenthesis.rb +1 -3
  17. data/features/support/1_file_with_bad_square_brackets/bad_square_brackets.rb +62 -0
  18. data/features/support/1_file_with_bad_ternary_colon_spacing/bad_ternary_colon_spacing.rb +31 -0
  19. data/features/support/1_good_simple_file/simple_project.rb +5 -0
  20. data/features/support/1_long_file_with_indentation/my_project.rb +56 -0
  21. data/features/support/common.rb +74 -1
  22. data/features/support/env.rb +3 -1
  23. data/features/support/world.rb +0 -52
  24. data/lib/tailor.rb +132 -41
  25. data/lib/tailor/file_line.rb +66 -177
  26. data/lib/tailor/indentation.rb +251 -0
  27. data/lib/tailor/spacing.rb +243 -0
  28. data/lib/tasks/metrics.rake +23 -0
  29. data/lib/tasks/stats.rake +11 -0
  30. data/logic.txt +30 -0
  31. data/ruby-style-checker.rb +77 -46
  32. data/spec/file_line_spec.rb +18 -193
  33. data/spec/indentation_spec.rb +259 -0
  34. data/spec/spacing/colon_spacing_spec.rb +71 -0
  35. data/spec/spacing/comma_spacing_spec.rb +159 -0
  36. data/spec/spacing/curly_brace_spacing_spec.rb +258 -0
  37. data/spec/spacing/parentheses_spacing_spec.rb +28 -0
  38. data/spec/spacing/square_bracket_spacing_spec.rb +116 -0
  39. data/spec/spacing_spec.rb +167 -0
  40. data/spec/spec_helper.rb +4 -0
  41. data/spec/tailor_spec.rb +2 -2
  42. metadata +73 -38
  43. data/features/support/1_good_simple_file/my_project.rb +0 -7
  44. data/lib/tailor/indentation_checker.rb +0 -27
@@ -0,0 +1,251 @@
1
+ require 'tailor/spacing'
2
+
3
+ module Tailor
4
+
5
+ # Provides methods for checking indentation problems in a FileLine.
6
+ module Indentation
7
+ include Tailor::Spacing
8
+
9
+ INDENT_SIZE = 2
10
+ HARD_TAB_SIZE = 4
11
+
12
+ INDENT_EXPRESSIONS = [
13
+ /^class\b/,
14
+ /^module\b/,
15
+ /^def\b/,
16
+ /(=\s*|^)if\b/,
17
+ /(=\s*|^)until\b/,
18
+ /(=\s*|^)for\b/,
19
+ /(=\s*|^)unless\b/,
20
+ /(=\s*|^)while\b/,
21
+ /(=\s*|^)begin\b/,
22
+ /(=\s*|^)case\b/,
23
+ /\bthen\b/,
24
+ # /^rescue\b/,
25
+ /\bdo\b/,
26
+ # /^else\b/,
27
+ # /^elsif\b/,
28
+ # /^ensure\b/,
29
+ # /\bwhen\b/,
30
+ /\{[^\}]*$/,
31
+ /\[[^\]]*$/
32
+ ]
33
+
34
+ OUTDENT_EXPRESSIONS = [
35
+ /^rescue\b/,
36
+ /^ensure\b/,
37
+ /^elsif\b/,
38
+ # /^end\b/,
39
+ /^else\b/,
40
+ /\bwhen\b/,
41
+ # /^[^\{]*\}/, # matches and end } when no { appears
42
+ # /^[^\[]*\]/
43
+ ]
44
+
45
+ END_EXPRESSIONS = [
46
+ /^end\b/,
47
+ /^[^\{]*\}/, # matches and end } when no { appears
48
+ /^[^\[]*\]/ # matches and end ] when no [ appears
49
+ ]
50
+
51
+ ##
52
+ # Determines the number of spaces the line is indented.
53
+ #
54
+ # @return [Number] Returns the number of spaces the line is indented.
55
+ def indented_spaces
56
+ # Find out how many spaces exist at the beginning of the line
57
+ spaces = self.scan(/^\x20+/).first
58
+
59
+ unless spaces.nil?
60
+ return spaces.length
61
+ end
62
+
63
+ return 0
64
+ end
65
+
66
+ ##
67
+ # Determines the level to which the line is indented. For Ruby, this
68
+ # should be 2 spaces. Note that this treats lines that are indented an
69
+ # odd number of spaces as a multiple of 0.5 levels of indentation.
70
+ #
71
+ # @return [Float] The level.
72
+ def is_at_level
73
+ spaces = indented_spaces
74
+
75
+ if spaces.eql? 0
76
+ return 0.0
77
+ end
78
+
79
+ return spaces.to_f / 2.0
80
+ end
81
+
82
+ ##
83
+ # Checks to see if the line contains a statement that should be indented.
84
+ #
85
+ # @return [Boolean] True if the line contains one of the statements and
86
+ # does not contain 'end'.
87
+ def indent?
88
+ return false if self.comment_line?
89
+
90
+ INDENT_EXPRESSIONS.each do |regexp|
91
+ result = self.strip.scan(regexp)
92
+
93
+ if(self.strip =~ regexp && !(self =~ /\s+end\s*$/))
94
+ return true
95
+ end
96
+ end
97
+
98
+ return false
99
+ end
100
+
101
+ ##
102
+ # Checks to see if the line contains a statement that should be outdented.
103
+ #
104
+ # @return [Boolean] True if the line contains one of the statements.
105
+ def outdent?
106
+ return false if self.comment_line?
107
+
108
+ OUTDENT_EXPRESSIONS.each do |regexp|
109
+ result = self.strip.scan(regexp)
110
+
111
+ # If it does contain an expression, set the proper level to be out 1.0.
112
+ unless result.empty?
113
+ return true
114
+ end
115
+ end
116
+
117
+ return false
118
+ end
119
+
120
+ ##
121
+ # Checks to see if the line contains a statement that ends a code chunk:
122
+ # end, ], or }.
123
+ #
124
+ # @return [Boolean] True if the line contains one of the statements.
125
+ def contains_end?
126
+ return false if self.comment_line?
127
+
128
+ END_EXPRESSIONS.each do |regexp|
129
+ result = self.strip.scan(regexp)
130
+
131
+ # If it does contain an expression, set the proper level to be out 1.0.
132
+ unless result.empty?
133
+ #@logger.debug "Found match: #{regexp}"
134
+ return true
135
+ end
136
+ end
137
+
138
+ return false
139
+ end
140
+
141
+ ##
142
+ # Simply compares the level the line is at to the parameter that's passed
143
+ # in. The proper level is maintained outside of this module.
144
+ #
145
+ # @return [Boolean] True if level of the line doesn't match the level
146
+ # passed in. Also returns true if the line is an empty line, since that
147
+ # line doens't need to be checked.
148
+ def at_improper_level? proper_level
149
+ current_level = self.is_at_level
150
+
151
+ if current_level == proper_level or self.empty_line?
152
+ return false
153
+ end
154
+
155
+ message = "Line is at level #{current_level}, "
156
+ message += "but should be at level #{proper_level}:"
157
+ @line_problem_count += 1
158
+ print_problem message
159
+
160
+ return true
161
+ end
162
+
163
+ def ends_with_operator?
164
+ if self.comment_line?
165
+ return false
166
+ end
167
+
168
+ result = false
169
+
170
+ OPERATORS.each_pair do |op_family, op_values|
171
+ op_values.each do |op|
172
+ match = self.scan(/.*#{Regexp.escape(op)}\s*$/)
173
+
174
+ next if op == '?' and self.question_mark_method?
175
+
176
+ unless match.empty?
177
+ logger = Logger.new(STDOUT)
178
+ logger.debug "Matched on op: #{op}"
179
+ result = true
180
+ end
181
+ end
182
+ end
183
+
184
+ return result
185
+ end
186
+
187
+ def ends_with_comma?
188
+ if self.comment_line?
189
+ return false
190
+ end
191
+
192
+ unless self.scan(/.*,\s*$/).empty?
193
+ puts "Ends with comma."
194
+ return true
195
+ end
196
+
197
+ return false
198
+ end
199
+
200
+ def ends_with_backslash?
201
+ if self.comment_line?
202
+ return false
203
+ end
204
+
205
+ unless self.scan(/.*\\\s*$/).empty?
206
+ puts "Ends with backslash."
207
+ return true
208
+ end
209
+
210
+ return false
211
+ end
212
+
213
+ def unclosed_parenthesis?
214
+ if self.comment_line?
215
+ return false
216
+ end
217
+
218
+ unless self.scan(/\([^\)]*(?!=\))\s*$/).empty?
219
+ puts "Ends with unclose parenthesis."
220
+ return true
221
+ end
222
+
223
+ return false
224
+ end
225
+
226
+ def only_closed_parenthesis?
227
+ if self.comment_line?
228
+ return false
229
+ end
230
+
231
+ unless self.scan(/^\s*[^\(](\w*|\s*)\)/).empty?
232
+ return true
233
+ end
234
+
235
+ return false
236
+ end
237
+
238
+ def multi_line_statement?
239
+ if self.comment_line?
240
+ return false
241
+ end
242
+
243
+ if self.ends_with_operator? or self.ends_with_comma? or
244
+ self.ends_with_backslash? or self.unclosed_parenthesis?
245
+ return true
246
+ end
247
+
248
+ return false
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,243 @@
1
+ require 'tailor'
2
+ require 'tailor/file_line'
3
+
4
+ module Tailor
5
+
6
+ # This module provides methods for detecting spacing problems on a single
7
+ # line in a file. The real intent here is to mix in to the FileLine class.
8
+ module Spacing
9
+ # TODO: Add skipping of comment lines.
10
+ SPACING_CONDITIONS = {
11
+ :more_than_one_space_after_comma => [
12
+ /\,\x20{2,}(\w|'|"|:).*((?:(?!#\s*)).)*$/,
13
+ "[Spacing] Line has a comma with > 1 space after it"
14
+ ],
15
+ :no_space_after_comma => [
16
+ /\,\x20{0}\S/,
17
+ "[Spacing] Line has a comma with 0 spaces after it"
18
+ ],
19
+ :space_before_comma => [
20
+ /\S\x20+\,/,
21
+ "[Spacing] Line has at least one space before a comma"
22
+ ],
23
+ :space_after_open_parenthesis => [
24
+ /\(\x20+/,
25
+ "[Spacing] Line has a '(' with spaces after it"
26
+ ],
27
+ :space_before_closed_parenthesis => [
28
+ /^\s*[^#]\w+.*\x20+\)/,
29
+ "[Spacing] Line has a ')' with spaces before it"
30
+ ],
31
+ :space_around_open_bracket => [
32
+ /^\s*[^#](\w+\x20+\[|.*\[\x20+)/,
33
+ "[Spacing] Line has a '[' with at least 1 space around it"
34
+ ],
35
+ :space_before_closed_bracket => [
36
+ /^\s*[^#]\w+.*\x20+\]/,
37
+ "[Spacing] Line has a ']' with spaces before it"
38
+ ],
39
+ :hard_tabbed => [
40
+ /\t+/,
41
+ "[Spacing] Line contains hard tabs"
42
+ ],
43
+ :trailing_whitespace => [
44
+ /(\x20+|\x09+)$/,
45
+ #"[Spacing] Line contains #{trailing_whitespace_count} " +
46
+ "[Spacing] Line contains trailing whitespaces"
47
+ ],
48
+ :no_space_around_open_curly_brace => [
49
+ /^\s*((?:(?!def).)*)(=|\w)\x20{0}\{|\{\x20{0}(\||:|"|')/,
50
+ "[Spacing] Line contains 0 spaces on at least one side of a '{'"
51
+ ],
52
+ :no_space_before_closed_curly_brace => [
53
+ /^\s*((?:(?!#\{).)*)(?:(?!\{)\S)\x20{0}\}/,
54
+ "[Spacing] Line contains 0 spaces before a '}'"
55
+ ],
56
+ :more_than_one_space_around_open_curly_brace => [
57
+ /\w\x20{2,}\{|\{\x20{2,}\|/,
58
+ "[Spacing] Line contains >1 spaces around a '{'"
59
+ ],
60
+ :more_than_one_space_before_closed_curly_brace => [
61
+ /\w\x20{2,}\}\s*$/,
62
+ "[Spacing] Line contains >1 spaces before a '}'"
63
+ ],
64
+ :not_one_space_around_ternary_colon => [
65
+ /^.*\?.*\w((\x20{0}|\x20{2,}):(?!:)|[^:|\[]:(\x20{0}|\x20{2,})\w)/,
66
+ "[Spacing] Line contains ternary ':' with not 1 space around it"
67
+ ]
68
+ }
69
+
70
+ ##
71
+ # Detect spacing problems around all predefined bad cases.
72
+ #
73
+ # @return [Number] The number of problems discovered during detection.
74
+ def spacing_problems
75
+ problem_count = 0
76
+
77
+ # Disregard text in regexps
78
+ self.gsub!(/\/.*?\//, "''")
79
+ self.gsub!(/'.*?'/, "''")
80
+
81
+ SPACING_CONDITIONS.each_pair do |condition, values|
82
+ unless self.scan(values.first).empty?
83
+ problem_count += 1
84
+ @line_problem_count += 1
85
+ print_problem values[1]
86
+ end
87
+ end
88
+ problem_count
89
+ end
90
+
91
+ ##
92
+ # Checks to see if there's whitespace at the end of the line. Prints the
93
+ # number of whitespaces at the end of the line.
94
+ #
95
+ # @return [Boolean] Returns true if there's whitespace at the end of the
96
+ # line.
97
+ =begin
98
+ def trailing_whitespace?
99
+ count = self.trailing_whitespace_count
100
+
101
+ if count > 0
102
+ @line_problem_count += 1
103
+ print_problem "Line contains #{count} trailing whitespace(s):"
104
+ return true
105
+ end
106
+
107
+ return false
108
+ end
109
+ =end
110
+ ##
111
+ # Checks to see if the line has trailing whitespace at the end of it. Note
112
+ # that this excludes empty lines that have spaces on them!
113
+ #
114
+ # @return [Number] Returns the number of trailing spaces at the end of the
115
+ # line.
116
+ def trailing_whitespace_count
117
+ spaces = self.scan(/(\x20+|\x09+)$/)
118
+
119
+ if spaces.first.eql? nil
120
+ return 0
121
+ end
122
+
123
+ return spaces.first.first.length
124
+ end
125
+ module_function :trailing_whitespace_count
126
+
127
+ ##
128
+ # Checks to see if there's no spaces before a given string. If the line
129
+ # being checked is a method with a question mark at the end of it, this
130
+ # skips checking the line.
131
+ #
132
+ # @param [String] string The string to check for spaces before.
133
+ # @return [Boolean] True if there are no spaces before the string.
134
+ def no_space_before? string
135
+ # Get out if the check is for a '?' and that's part of a method name.
136
+ if self.question_mark_method?
137
+ return false
138
+ end
139
+
140
+ # Get out if this line is a comment line
141
+ if self.comment_line?
142
+ return false
143
+ end
144
+
145
+ # Get out if the string is within another string
146
+ if word_is_in_string? string
147
+ return false
148
+ end
149
+
150
+ counts = []
151
+ spaces_before(string).each { |s| counts << s }
152
+
153
+ result = false
154
+ counts.each do |count|
155
+ if count == 0
156
+ @line_problem_count += 1
157
+ print_problem "Line has a '#{string}' with 0 spaces before it:"
158
+ result = true
159
+ end
160
+ end
161
+
162
+ result
163
+ end
164
+
165
+ ##
166
+ # Checks to see if there's no spaces after a given string.
167
+ #
168
+ # @param [String] string The string to check for spaces after.
169
+ # @return [Boolean] True if there are no spaces after the string.
170
+ def no_space_after? string
171
+ # Get out if the check is for a '?' and that's part of a method name.
172
+ if self.question_mark_method?
173
+ return false
174
+ end
175
+
176
+ # Get out if this line is a comment line
177
+ if self.comment_line?
178
+ return false
179
+ end
180
+
181
+ # Get out if the string is within another string
182
+ if word_is_in_string? string
183
+ return false
184
+ end
185
+
186
+ # Get out if the string is within another string
187
+ if word_is_in_regexp? string
188
+ return false
189
+ end
190
+
191
+ counts = []
192
+ spaces_after(string).each { |s| counts << s }
193
+
194
+ result = false
195
+ counts.each do |count|
196
+ if count == 0
197
+ @line_problem_count += 1
198
+ print_problem "Line has a '#{string}' with 0 spaces after it:"
199
+ result = true
200
+ end
201
+ end
202
+
203
+ result
204
+ end
205
+
206
+ ##
207
+ # Gets the number of spaces after a string.
208
+ #
209
+ # @param [String] string The string to check for spaces after.
210
+ # @return [Array<Number>] An array that holds the number of spaces after
211
+ # every time the given string appears in a line.
212
+ def spaces_after string
213
+ # Get out if this line is a comment line
214
+ if self.comment_line?
215
+ return false
216
+ end
217
+
218
+ right_side_match = Regexp.new(Regexp.escape(string) + '\x20*')
219
+
220
+ occurences = self.scan(right_side_match)
221
+
222
+ results = []
223
+ occurences.each do |o|
224
+ string_spaces = o.sub(string, '')
225
+ results << string_spaces.size
226
+ end
227
+
228
+ results
229
+ end
230
+
231
+ ##
232
+ # Checks to see if the line contains a method name with a ?.
233
+ #
234
+ # @return [Boolean] True if the line contains a method line include?.
235
+ def question_mark_method?
236
+ if self.scan(/[a-zA-Z|_]\w*\?/).empty?
237
+ return false
238
+ end
239
+
240
+ return true
241
+ end
242
+ end
243
+ end