tailor 1.3.1 → 1.4.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.
- 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
|