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.
- data/History.txt +8 -0
- data/Manifest.txt +22 -2
- data/PostInstall.txt +4 -0
- data/README.rdoc +7 -6
- data/Rakefile +7 -1
- data/bin/tailor +21 -5
- data/features/indentation.feature +22 -0
- data/features/spacing.feature +13 -18
- data/features/spacing/commas.feature +44 -0
- data/features/step_definitions/indentation_steps.rb +139 -0
- data/features/step_definitions/spacing/commas_steps.rb +14 -0
- data/features/step_definitions/spacing_steps.rb +1 -36
- data/features/support/1_file_with_bad_comma_spacing/bad_comma_spacing.rb +43 -5
- data/features/support/1_file_with_bad_curly_brace_spacing/bad_curly_brace_spacing.rb +60 -0
- data/features/support/1_file_with_bad_operator_spacing/bad_op_spacing.rb +31 -0
- data/features/support/1_file_with_bad_parenthesis/bad_parenthesis.rb +1 -3
- data/features/support/1_file_with_bad_square_brackets/bad_square_brackets.rb +62 -0
- data/features/support/1_file_with_bad_ternary_colon_spacing/bad_ternary_colon_spacing.rb +31 -0
- data/features/support/1_good_simple_file/simple_project.rb +5 -0
- data/features/support/1_long_file_with_indentation/my_project.rb +56 -0
- data/features/support/common.rb +74 -1
- data/features/support/env.rb +3 -1
- data/features/support/world.rb +0 -52
- data/lib/tailor.rb +132 -41
- data/lib/tailor/file_line.rb +66 -177
- data/lib/tailor/indentation.rb +251 -0
- data/lib/tailor/spacing.rb +243 -0
- data/lib/tasks/metrics.rake +23 -0
- data/lib/tasks/stats.rake +11 -0
- data/logic.txt +30 -0
- data/ruby-style-checker.rb +77 -46
- data/spec/file_line_spec.rb +18 -193
- data/spec/indentation_spec.rb +259 -0
- data/spec/spacing/colon_spacing_spec.rb +71 -0
- data/spec/spacing/comma_spacing_spec.rb +159 -0
- data/spec/spacing/curly_brace_spacing_spec.rb +258 -0
- data/spec/spacing/parentheses_spacing_spec.rb +28 -0
- data/spec/spacing/square_bracket_spacing_spec.rb +116 -0
- data/spec/spacing_spec.rb +167 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/tailor_spec.rb +2 -2
- metadata +73 -38
- data/features/support/1_good_simple_file/my_project.rb +0 -7
- 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
|