tailor 1.3.1 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/Gemfile.lock +27 -25
- data/History.md +36 -0
- data/README.md +10 -4
- data/Rakefile +6 -6
- data/features/support/env.rb +2 -2
- data/features/support/legacy/bad_ternary_colon_spacing.rb +1 -1
- data/features/support/legacy/long_file_with_indentation.rb +2 -2
- data/features/support/world.rb +1 -1
- data/features/valid_ruby.feature +1 -1
- data/lib/tailor/cli/options.rb +19 -1
- data/lib/tailor/configuration.rb +2 -2
- data/lib/tailor/configuration/style.rb +6 -0
- data/lib/tailor/lexed_line.rb +2 -2
- data/lib/tailor/lexer/lexer_constants.rb +1 -0
- data/lib/tailor/lexer/token.rb +2 -2
- data/lib/tailor/logger.rb +2 -2
- data/lib/tailor/rake_task.rb +2 -2
- data/lib/tailor/ruler.rb +0 -1
- data/lib/tailor/rulers/allow_camel_case_methods_ruler.rb +0 -1
- data/lib/tailor/rulers/allow_conditional_parentheses.rb +65 -0
- data/lib/tailor/rulers/allow_unnecessary_double_quotes_ruler.rb +61 -0
- data/lib/tailor/rulers/allow_unnecessary_interpolation_ruler.rb +87 -0
- data/lib/tailor/rulers/indentation_spaces_ruler.rb +40 -50
- data/lib/tailor/rulers/indentation_spaces_ruler/argument_alignment.rb +63 -0
- data/lib/tailor/rulers/indentation_spaces_ruler/ast_xml.rb +89 -0
- data/lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb +2 -14
- data/lib/tailor/rulers/indentation_spaces_ruler/line_continuations.rb +58 -0
- data/lib/tailor/rulers/max_code_lines_in_class_ruler.rb +3 -3
- data/lib/tailor/rulers/max_code_lines_in_method_ruler.rb +3 -3
- data/lib/tailor/rulers/spaces_after_comma_ruler.rb +3 -3
- data/lib/tailor/rulers/spaces_after_conditional_ruler.rb +3 -3
- data/lib/tailor/rulers/spaces_after_lbrace_ruler.rb +3 -3
- data/lib/tailor/rulers/spaces_after_lbracket_ruler.rb +3 -3
- data/lib/tailor/rulers/spaces_after_lparen_ruler.rb +3 -3
- data/lib/tailor/rulers/spaces_before_comma_ruler.rb +3 -3
- data/lib/tailor/rulers/spaces_before_rbrace_ruler.rb +2 -2
- data/lib/tailor/rulers/spaces_in_empty_braces_ruler.rb +2 -2
- data/lib/tailor/tailorrc.erb +28 -0
- data/lib/tailor/version.rb +1 -1
- data/spec/functional/conditional_parentheses_spec.rb +325 -0
- data/spec/functional/conditional_spacing_spec.rb +66 -42
- data/spec/functional/configuration_spec.rb +1 -1
- data/spec/functional/horizontal_spacing/braces_spec.rb +10 -9
- data/spec/functional/horizontal_spacing/hard_tabs_spec.rb +4 -4
- data/spec/functional/horizontal_spacing_spec.rb +5 -5
- data/spec/functional/indentation_spacing/argument_alignment_spec.rb +511 -0
- data/spec/functional/indentation_spacing/line_continuations_spec.rb +181 -0
- data/spec/functional/indentation_spacing_spec.rb +17 -0
- data/spec/functional/string_interpolation_spec.rb +117 -0
- data/spec/functional/string_quoting_spec.rb +97 -0
- data/spec/functional/vertical_spacing/class_length_spec.rb +1 -1
- data/spec/spec_helper.rb +8 -2
- data/spec/support/argument_alignment_cases.rb +93 -0
- data/spec/support/bad_indentation_cases.rb +20 -20
- data/spec/support/conditional_parentheses_cases.rb +60 -0
- data/spec/support/good_indentation_cases.rb +31 -31
- data/spec/support/horizontal_spacing_cases.rb +1 -1
- data/spec/support/line_indentation_cases.rb +71 -0
- data/spec/support/string_interpolation_cases.rb +45 -0
- data/spec/support/string_quoting_cases.rb +25 -0
- data/spec/unit/tailor/configuration/file_set_spec.rb +3 -3
- data/spec/unit/tailor/configuration/style_spec.rb +24 -21
- data/spec/unit/tailor/configuration_spec.rb +4 -1
- data/spec/unit/tailor/formatter_spec.rb +10 -10
- data/spec/unit/tailor/reporter_spec.rb +0 -1
- data/spec/unit/tailor/rulers/indentation_spaces_ruler/indentation_manager_spec.rb +0 -11
- data/spec/unit/tailor/rulers/indentation_spaces_ruler_spec.rb +1 -1
- data/spec/unit/tailor/version_spec.rb +1 -1
- data/tailor.gemspec +1 -0
- metadata +72 -33
- data/.infinity_test +0 -4
data/lib/tailor/ruler.rb
CHANGED
@@ -0,0 +1,65 @@
|
|
1
|
+
require_relative '../ruler'
|
2
|
+
|
3
|
+
class Tailor
|
4
|
+
module Rulers
|
5
|
+
class AllowConditionalParenthesesRuler < Tailor::Ruler
|
6
|
+
def initialize(style, options)
|
7
|
+
super(style, options)
|
8
|
+
add_lexer_observers :nl
|
9
|
+
end
|
10
|
+
|
11
|
+
def nl_update(current_lexed_line, lineno, _)
|
12
|
+
measure(current_lexed_line, lineno)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Checks to see if a conditional is unnecessarily wrapped in parentheses.
|
16
|
+
#
|
17
|
+
# @param [Fixnum] line The current lexed line.
|
18
|
+
# @param [Fixnum] lineno Line the problem was found on.
|
19
|
+
def measure(line, lineno)
|
20
|
+
return if @config
|
21
|
+
return unless line.any? { |t| conditional?(t) }
|
22
|
+
if tokens_before_lparen?(line) and ! tokens_after_rparen?(line)
|
23
|
+
column = lparen_column(line)
|
24
|
+
@problems << Problem.new('conditional_parentheses', lineno, column,
|
25
|
+
"Parentheses around conditional expression at column #{column}.",
|
26
|
+
@options[:level])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def conditional?(token)
|
33
|
+
token[1] == :on_kw and %w{case if unless while}.include?(token[2])
|
34
|
+
end
|
35
|
+
|
36
|
+
def lparen?(token)
|
37
|
+
token[1] == :on_lparen
|
38
|
+
end
|
39
|
+
|
40
|
+
def lparen_column(tokens)
|
41
|
+
tokens.find { |t| lparen?(t) }[0][1] + 1
|
42
|
+
end
|
43
|
+
|
44
|
+
def tokens_before_lparen?(tokens)
|
45
|
+
without_spaces(
|
46
|
+
tokens.select do |t|
|
47
|
+
true if (conditional?(t))..(lparen?(t))
|
48
|
+
end.tap { |t| t.shift; t.pop }
|
49
|
+
).empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
def tokens_after_rparen?(tokens)
|
53
|
+
without_spaces(
|
54
|
+
tokens.reverse.tap do |nl|
|
55
|
+
nl.shift
|
56
|
+
end.take_while { |t| t[1] != :on_rparen }
|
57
|
+
).any?
|
58
|
+
end
|
59
|
+
|
60
|
+
def without_spaces(tokens)
|
61
|
+
tokens.reject { |t| t[1] == :on_sp }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative '../ruler'
|
2
|
+
|
3
|
+
class Tailor
|
4
|
+
module Rulers
|
5
|
+
class AllowUnnecessaryDoubleQuotesRuler < Tailor::Ruler
|
6
|
+
def initialize(config, options)
|
7
|
+
super(config, options)
|
8
|
+
add_lexer_observers :nl
|
9
|
+
end
|
10
|
+
|
11
|
+
def nl_update(lexed_line, lineno, _)
|
12
|
+
quotes(lexed_line).each do |quote|
|
13
|
+
unless contains_embedded_expression?(quote) ||
|
14
|
+
contains_escape_sequence?(quote)
|
15
|
+
measure(lineno, column(quote.first))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Checks to see if the double_quotes are unnecessary.
|
21
|
+
#
|
22
|
+
# @param [Fixnum] lineno Line the problem was found on.
|
23
|
+
# @param [Fixnum] column Column the problem was found on.
|
24
|
+
def measure(lineno, column)
|
25
|
+
@problems << Problem.new('unnecessary_double_quotes', lineno, column,
|
26
|
+
"Unnecessary double quotes at column #{column}, " +
|
27
|
+
'expected single quotes.', @options[:level])
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def contains_embedded_expression?(tokens)
|
33
|
+
tokens.any? { |t| t[1] == :on_embexpr_beg }
|
34
|
+
end
|
35
|
+
|
36
|
+
def contains_escape_sequence?(tokens)
|
37
|
+
tokens.any? do |t|
|
38
|
+
t[1] == :on_tstring_content and t[2].match(/\\[a-z]+/)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def quotes(tokens)
|
43
|
+
tokens.select do |t|
|
44
|
+
true if (double_quote_start?(t))..(double_quote_end?(t))
|
45
|
+
end.slice_before { |t| double_quote_start?(t) }.reject { |q| q.empty? }
|
46
|
+
end
|
47
|
+
|
48
|
+
def column(token)
|
49
|
+
token[0][1]
|
50
|
+
end
|
51
|
+
|
52
|
+
def double_quote_start?(token)
|
53
|
+
token[1] == :on_tstring_beg and token[2] == '"'
|
54
|
+
end
|
55
|
+
|
56
|
+
def double_quote_end?(token)
|
57
|
+
token[1] == :on_tstring_end and token[2] == '"'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require_relative '../ruler'
|
2
|
+
|
3
|
+
class Tailor
|
4
|
+
module Rulers
|
5
|
+
class AllowUnnecessaryInterpolationRuler < Tailor::Ruler
|
6
|
+
|
7
|
+
EVENTS = [
|
8
|
+
:on_embexpr_beg,
|
9
|
+
:on_embexpr_end,
|
10
|
+
:on_rbrace,
|
11
|
+
:on_tstring_beg,
|
12
|
+
:on_tstring_content,
|
13
|
+
:on_tstring_end
|
14
|
+
]
|
15
|
+
|
16
|
+
def initialize(config, options)
|
17
|
+
super(config, options)
|
18
|
+
reset_tokens
|
19
|
+
add_lexer_observers :ignored_nl, :nl
|
20
|
+
end
|
21
|
+
|
22
|
+
def ignored_nl_update(lexed_line, _, _)
|
23
|
+
add_string_tokens(lexed_line)
|
24
|
+
end
|
25
|
+
|
26
|
+
def nl_update(lexed_line, _, _)
|
27
|
+
add_string_tokens(lexed_line)
|
28
|
+
each_string(@tokens).each do |string|
|
29
|
+
measure(line_number(@tokens.first), string)
|
30
|
+
end
|
31
|
+
|
32
|
+
reset_tokens
|
33
|
+
end
|
34
|
+
|
35
|
+
# Checks if variables are interpolated unnecessarily.
|
36
|
+
#
|
37
|
+
# @param [Array] tokens The filtered tokens.
|
38
|
+
def measure(lineno, tokens)
|
39
|
+
return if @config
|
40
|
+
if no_content?(tokens) and one_expression?(tokens)
|
41
|
+
@problems << Problem.new('unnecessary_string_interpolation', lineno,
|
42
|
+
column(tokens.first), 'Variable interpolated unnecessarily',
|
43
|
+
@options[:level])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def add_string_tokens(lexed_line)
|
50
|
+
@tokens += string_tokens(lexed_line)
|
51
|
+
end
|
52
|
+
|
53
|
+
def column(token)
|
54
|
+
token.first.last + 1
|
55
|
+
end
|
56
|
+
|
57
|
+
def each_string(tokens)
|
58
|
+
tokens.select do |t|
|
59
|
+
true if (t[1] == :on_tstring_beg)..(t[1] == :on_tstring_end)
|
60
|
+
end.slice_before { |t| t[1] == :on_tstring_beg }
|
61
|
+
end
|
62
|
+
|
63
|
+
def line_number(token)
|
64
|
+
token.first.first
|
65
|
+
end
|
66
|
+
|
67
|
+
def no_content?(tokens)
|
68
|
+
! tokens.map { |t| t[1] }.include?(:on_tstring_content)
|
69
|
+
end
|
70
|
+
|
71
|
+
def one_expression?(tokens)
|
72
|
+
tokens.select { |t| t[1] == :on_embexpr_beg }.size == 1 and
|
73
|
+
tokens.select do |t|
|
74
|
+
t[1] == :on_embexpr_end or t[1] == :on_rbrace
|
75
|
+
end.any?
|
76
|
+
end
|
77
|
+
|
78
|
+
def reset_tokens
|
79
|
+
@tokens = []
|
80
|
+
end
|
81
|
+
|
82
|
+
def string_tokens(lexed_line)
|
83
|
+
lexed_line.select { |t| EVENTS.include?(t[1]) }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -1,7 +1,10 @@
|
|
1
|
+
require 'nokogiri'
|
1
2
|
require_relative '../ruler'
|
2
3
|
require_relative '../lexed_line'
|
3
4
|
require_relative '../lexer/token'
|
5
|
+
require_relative 'indentation_spaces_ruler/argument_alignment'
|
4
6
|
require_relative 'indentation_spaces_ruler/indentation_manager'
|
7
|
+
require_relative 'indentation_spaces_ruler/line_continuations'
|
5
8
|
|
6
9
|
class Tailor
|
7
10
|
module Rulers
|
@@ -12,6 +15,7 @@ class Tailor
|
|
12
15
|
:comment,
|
13
16
|
:embexpr_beg,
|
14
17
|
:embexpr_end,
|
18
|
+
:file_beg,
|
15
19
|
:ignored_nl,
|
16
20
|
:kw,
|
17
21
|
:lbrace,
|
@@ -67,21 +71,17 @@ class Tailor
|
|
67
71
|
# More info: https://bugs.ruby-lang.org/issues/6211
|
68
72
|
def embexpr_end_update(current_lexed_line, lineno, column)
|
69
73
|
@embexpr_nesting.pop
|
70
|
-
|
71
|
-
if @manager.multi_line_braces?(lineno)
|
72
|
-
log 'End of multi-line braces!'
|
73
|
-
|
74
|
-
if current_lexed_line.only_embexpr_end?
|
75
|
-
@manager.amount_to_change_this -= 1
|
76
|
-
msg = 'lonely embexpr_end. '
|
77
|
-
msg << "change_this -= 1 -> #{@manager.amount_to_change_this}"
|
78
|
-
log msg
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
74
|
@manager.update_for_closing_reason(:on_embexpr_end, current_lexed_line)
|
83
75
|
end
|
84
76
|
|
77
|
+
def file_beg_update(file_name)
|
78
|
+
# For statements that continue over multiple lines we may want to treat
|
79
|
+
# the second and subsequent lines differently and ident them further,
|
80
|
+
# controlled by the :line_continuations option.
|
81
|
+
@lines = LineContinuations.new(file_name) if line_continuations?
|
82
|
+
@args = ArgumentAlignment.new(file_name) if argument_alignment?
|
83
|
+
end
|
84
|
+
|
85
85
|
def ignored_nl_update(current_lexed_line, lineno, column)
|
86
86
|
log "indent reasons on entry: #{@manager.indent_reasons}"
|
87
87
|
|
@@ -110,7 +110,6 @@ class Tailor
|
|
110
110
|
end
|
111
111
|
|
112
112
|
@manager.update_actual_indentation(current_lexed_line)
|
113
|
-
@manager.set_up_line_transition
|
114
113
|
measure(lineno, column)
|
115
114
|
|
116
115
|
log "indent reasons on exit: #{@manager.indent_reasons}"
|
@@ -172,8 +171,6 @@ class Tailor
|
|
172
171
|
last[:event_type], current_lexed_line)
|
173
172
|
end
|
174
173
|
|
175
|
-
@manager.set_up_line_transition
|
176
|
-
|
177
174
|
unless current_lexed_line.end_of_multi_line_string?
|
178
175
|
measure(lineno, column)
|
179
176
|
end
|
@@ -205,47 +202,14 @@ class Tailor
|
|
205
202
|
return
|
206
203
|
end
|
207
204
|
|
208
|
-
if @manager.multi_line_braces?(lineno)
|
209
|
-
log 'End of multi-line braces!'
|
210
|
-
|
211
|
-
if current_lexed_line.only_rbrace?
|
212
|
-
@manager.amount_to_change_this -= 1
|
213
|
-
msg = 'lonely rbrace. '
|
214
|
-
msg << "change_this -= 1 -> #{@manager.amount_to_change_this}"
|
215
|
-
log msg
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
205
|
@manager.update_for_closing_reason(:on_rbrace, current_lexed_line)
|
220
206
|
end
|
221
207
|
|
222
208
|
def rbracket_update(current_lexed_line, lineno, column)
|
223
|
-
if @manager.multi_line_brackets?(lineno)
|
224
|
-
log 'End of multi-line brackets!'
|
225
|
-
|
226
|
-
if current_lexed_line.only_rbracket?
|
227
|
-
@manager.amount_to_change_this -= 1
|
228
|
-
msg = 'lonely rbracket. '
|
229
|
-
msg << "change_this -= 1 -> #{@manager.amount_to_change_this}"
|
230
|
-
log msg
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
209
|
@manager.update_for_closing_reason(:on_rbracket, current_lexed_line)
|
235
210
|
end
|
236
211
|
|
237
212
|
def rparen_update(current_lexed_line, lineno, column)
|
238
|
-
if @manager.multi_line_parens?(lineno)
|
239
|
-
log 'End of multi-line parens!'
|
240
|
-
|
241
|
-
if current_lexed_line.only_rparen?
|
242
|
-
@manager.amount_to_change_this -= 1
|
243
|
-
msg = 'lonely rparen. '
|
244
|
-
msg << "change_this -= 1 -> #{@manager.amount_to_change_this}"
|
245
|
-
log msg
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
213
|
@manager.update_for_closing_reason(:on_rparen, current_lexed_line)
|
250
214
|
end
|
251
215
|
|
@@ -272,6 +236,29 @@ class Tailor
|
|
272
236
|
!@tstring_nesting.empty?
|
273
237
|
end
|
274
238
|
|
239
|
+
def line_continuations?
|
240
|
+
@options[:line_continuations] and @options[:line_continuations] != :off
|
241
|
+
end
|
242
|
+
|
243
|
+
def with_line_continuations(lineno, should_be_at)
|
244
|
+
return should_be_at unless line_continuations?
|
245
|
+
if @lines.line_is_continuation?(lineno) and
|
246
|
+
@lines.line_has_nested_statements?(lineno)
|
247
|
+
should_be_at + @config
|
248
|
+
else
|
249
|
+
should_be_at
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def argument_alignment?
|
254
|
+
@options[:argument_alignment] and @options[:argument_alignment] != :off
|
255
|
+
end
|
256
|
+
|
257
|
+
def with_argument_alignment(lineno, should_be_at)
|
258
|
+
return should_be_at unless argument_alignment?
|
259
|
+
@args.expected_column(lineno, should_be_at)
|
260
|
+
end
|
261
|
+
|
275
262
|
# Checks if the line's indentation level is appropriate.
|
276
263
|
#
|
277
264
|
# @param [Fixnum] lineno The line the potential problem is on.
|
@@ -279,9 +266,12 @@ class Tailor
|
|
279
266
|
def measure(lineno, column)
|
280
267
|
log 'Measuring...'
|
281
268
|
|
282
|
-
|
269
|
+
should_be_at = with_line_continuations(lineno, @manager.should_be_at)
|
270
|
+
should_be_at = with_argument_alignment(lineno, should_be_at)
|
271
|
+
|
272
|
+
if @manager.actual_indentation != should_be_at
|
283
273
|
msg = "Line is indented to column #{@manager.actual_indentation}, "
|
284
|
-
msg << "but should be at #{
|
274
|
+
msg << "but should be at #{should_be_at}."
|
285
275
|
|
286
276
|
@problems << Problem.new(problem_type, lineno,
|
287
277
|
@manager.actual_indentation, msg, @options[:level])
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'ripper'
|
2
|
+
require_relative './ast_xml'
|
3
|
+
|
4
|
+
class Tailor
|
5
|
+
module Rulers
|
6
|
+
class IndentationSpacesRuler < Tailor::Ruler
|
7
|
+
|
8
|
+
# Determines whether arguments spread across lines are correctly
|
9
|
+
# aligned.
|
10
|
+
#
|
11
|
+
# Function calls with parentheses could be determined easily from
|
12
|
+
# the lexed line only but we need to support calls without parentheses
|
13
|
+
# too.
|
14
|
+
class ArgumentAlignment
|
15
|
+
|
16
|
+
include AstXml
|
17
|
+
|
18
|
+
def initialize(file_name)
|
19
|
+
@ast = build_xml(Ripper::SexpBuilder.new(File.read(file_name)).parse)
|
20
|
+
@lex = Ripper.lex(File.read(file_name))
|
21
|
+
end
|
22
|
+
|
23
|
+
def expected_column(lineno, should_be_at)
|
24
|
+
column = call_column(lineno) || declaration_column(lineno)
|
25
|
+
correct_for_literals(lineno, column) || should_be_at
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# sexp column offsets for string literals do not include the quote
|
31
|
+
def correct_for_literals(lineno, column)
|
32
|
+
tstring_index = @lex.index do |pos, token|
|
33
|
+
pos[0] == lineno and pos[1] == column and
|
34
|
+
token == :on_tstring_content
|
35
|
+
end
|
36
|
+
|
37
|
+
tstring_index ? @lex[tstring_index -1][0][1] : column
|
38
|
+
end
|
39
|
+
|
40
|
+
def call_column(lineno)
|
41
|
+
[
|
42
|
+
first_argument(:command_call, :args_add_block, lineno),
|
43
|
+
first_argument(:method_add_arg, :args_add_block, lineno)
|
44
|
+
].compact.min
|
45
|
+
end
|
46
|
+
|
47
|
+
def declaration_column(lineno)
|
48
|
+
first_argument(:def, :params, lineno)
|
49
|
+
end
|
50
|
+
|
51
|
+
def first_argument(parent, child, lineno)
|
52
|
+
method_defs = @ast.xpath("//#{parent}")
|
53
|
+
method_defs.map do |d|
|
54
|
+
d.xpath("descendant::#{child}[descendant::pos[@line = #{lineno}
|
55
|
+
]]/descendant::pos[position() = 1 and @line != #{lineno}]/
|
56
|
+
@column").first.to_s.to_i
|
57
|
+
end.reject { |c| c == 0 }.min
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|