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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/Gemfile.lock +27 -25
  4. data/History.md +36 -0
  5. data/README.md +10 -4
  6. data/Rakefile +6 -6
  7. data/features/support/env.rb +2 -2
  8. data/features/support/legacy/bad_ternary_colon_spacing.rb +1 -1
  9. data/features/support/legacy/long_file_with_indentation.rb +2 -2
  10. data/features/support/world.rb +1 -1
  11. data/features/valid_ruby.feature +1 -1
  12. data/lib/tailor/cli/options.rb +19 -1
  13. data/lib/tailor/configuration.rb +2 -2
  14. data/lib/tailor/configuration/style.rb +6 -0
  15. data/lib/tailor/lexed_line.rb +2 -2
  16. data/lib/tailor/lexer/lexer_constants.rb +1 -0
  17. data/lib/tailor/lexer/token.rb +2 -2
  18. data/lib/tailor/logger.rb +2 -2
  19. data/lib/tailor/rake_task.rb +2 -2
  20. data/lib/tailor/ruler.rb +0 -1
  21. data/lib/tailor/rulers/allow_camel_case_methods_ruler.rb +0 -1
  22. data/lib/tailor/rulers/allow_conditional_parentheses.rb +65 -0
  23. data/lib/tailor/rulers/allow_unnecessary_double_quotes_ruler.rb +61 -0
  24. data/lib/tailor/rulers/allow_unnecessary_interpolation_ruler.rb +87 -0
  25. data/lib/tailor/rulers/indentation_spaces_ruler.rb +40 -50
  26. data/lib/tailor/rulers/indentation_spaces_ruler/argument_alignment.rb +63 -0
  27. data/lib/tailor/rulers/indentation_spaces_ruler/ast_xml.rb +89 -0
  28. data/lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb +2 -14
  29. data/lib/tailor/rulers/indentation_spaces_ruler/line_continuations.rb +58 -0
  30. data/lib/tailor/rulers/max_code_lines_in_class_ruler.rb +3 -3
  31. data/lib/tailor/rulers/max_code_lines_in_method_ruler.rb +3 -3
  32. data/lib/tailor/rulers/spaces_after_comma_ruler.rb +3 -3
  33. data/lib/tailor/rulers/spaces_after_conditional_ruler.rb +3 -3
  34. data/lib/tailor/rulers/spaces_after_lbrace_ruler.rb +3 -3
  35. data/lib/tailor/rulers/spaces_after_lbracket_ruler.rb +3 -3
  36. data/lib/tailor/rulers/spaces_after_lparen_ruler.rb +3 -3
  37. data/lib/tailor/rulers/spaces_before_comma_ruler.rb +3 -3
  38. data/lib/tailor/rulers/spaces_before_rbrace_ruler.rb +2 -2
  39. data/lib/tailor/rulers/spaces_in_empty_braces_ruler.rb +2 -2
  40. data/lib/tailor/tailorrc.erb +28 -0
  41. data/lib/tailor/version.rb +1 -1
  42. data/spec/functional/conditional_parentheses_spec.rb +325 -0
  43. data/spec/functional/conditional_spacing_spec.rb +66 -42
  44. data/spec/functional/configuration_spec.rb +1 -1
  45. data/spec/functional/horizontal_spacing/braces_spec.rb +10 -9
  46. data/spec/functional/horizontal_spacing/hard_tabs_spec.rb +4 -4
  47. data/spec/functional/horizontal_spacing_spec.rb +5 -5
  48. data/spec/functional/indentation_spacing/argument_alignment_spec.rb +511 -0
  49. data/spec/functional/indentation_spacing/line_continuations_spec.rb +181 -0
  50. data/spec/functional/indentation_spacing_spec.rb +17 -0
  51. data/spec/functional/string_interpolation_spec.rb +117 -0
  52. data/spec/functional/string_quoting_spec.rb +97 -0
  53. data/spec/functional/vertical_spacing/class_length_spec.rb +1 -1
  54. data/spec/spec_helper.rb +8 -2
  55. data/spec/support/argument_alignment_cases.rb +93 -0
  56. data/spec/support/bad_indentation_cases.rb +20 -20
  57. data/spec/support/conditional_parentheses_cases.rb +60 -0
  58. data/spec/support/good_indentation_cases.rb +31 -31
  59. data/spec/support/horizontal_spacing_cases.rb +1 -1
  60. data/spec/support/line_indentation_cases.rb +71 -0
  61. data/spec/support/string_interpolation_cases.rb +45 -0
  62. data/spec/support/string_quoting_cases.rb +25 -0
  63. data/spec/unit/tailor/configuration/file_set_spec.rb +3 -3
  64. data/spec/unit/tailor/configuration/style_spec.rb +24 -21
  65. data/spec/unit/tailor/configuration_spec.rb +4 -1
  66. data/spec/unit/tailor/formatter_spec.rb +10 -10
  67. data/spec/unit/tailor/reporter_spec.rb +0 -1
  68. data/spec/unit/tailor/rulers/indentation_spaces_ruler/indentation_manager_spec.rb +0 -11
  69. data/spec/unit/tailor/rulers/indentation_spaces_ruler_spec.rb +1 -1
  70. data/spec/unit/tailor/version_spec.rb +1 -1
  71. data/tailor.gemspec +1 -0
  72. metadata +72 -33
  73. data/.infinity_test +0 -4
@@ -0,0 +1,89 @@
1
+ class Tailor
2
+ module Rulers
3
+ class IndentationSpacesRuler < Tailor::Ruler
4
+
5
+ # XXX: Reproducing the ast querying functions from foodcritic here. We
6
+ # either need to re-implement the queries not to rely on these functions
7
+ # or extract these functions to a shared gem.
8
+ module AstXml
9
+
10
+ def xml_array_node(doc, xml_node, child)
11
+ n = xml_create_node(doc, child)
12
+ xml_node.add_child(build_xml(child, doc, n))
13
+ end
14
+
15
+ def xml_create_node(doc, c)
16
+ Nokogiri::XML::Node.new(c.first.to_s.gsub(/[^a-z_]/, ''), doc)
17
+ end
18
+
19
+ def xml_document(doc, xml_node)
20
+ if doc.nil?
21
+ doc = Nokogiri::XML('<opt></opt>')
22
+ xml_node = doc.root
23
+ end
24
+ [doc, xml_node]
25
+ end
26
+
27
+ def xml_hash_node(doc, xml_node, child)
28
+ child.each do |c|
29
+ n = xml_create_node(doc, c)
30
+ c.drop(1).each do |a|
31
+ xml_node.add_child(build_xml(a, doc, n))
32
+ end
33
+ end
34
+ end
35
+
36
+ def xml_position_node(doc, xml_node, child)
37
+ pos = Nokogiri::XML::Node.new('pos', doc)
38
+ pos['line'] = child.first.to_s
39
+ pos['column'] = child[1].to_s
40
+ xml_node.add_child(pos)
41
+ end
42
+
43
+ def ast_hash_node?(node)
44
+ node.first.respond_to?(:first) and node.first.first == :assoc_new
45
+ end
46
+
47
+ def ast_node_has_children?(node)
48
+ node.respond_to?(:first) and ! node.respond_to?(:match)
49
+ end
50
+
51
+ # If the provided node is the line / column information.
52
+ def position_node?(node)
53
+ node.respond_to?(:length) and node.length == 2 and
54
+ node.respond_to?(:all?) and node.all? do |child|
55
+ child.respond_to?(:to_i)
56
+ end
57
+ end
58
+
59
+ def build_xml(node, doc = nil, xml_node=nil)
60
+ doc, xml_node = xml_document(doc, xml_node)
61
+
62
+ if node.respond_to?(:each)
63
+ # First child is the node name
64
+ node.drop(1) if node.first.is_a?(Symbol)
65
+ node.each do |child|
66
+ if position_node?(child)
67
+ xml_position_node(doc, xml_node, child)
68
+ else
69
+ if ast_node_has_children?(child)
70
+ # The AST structure is different for hashes so we have to treat
71
+ # them separately.
72
+ if ast_hash_node?(child)
73
+ xml_hash_node(doc, xml_node, child)
74
+ else
75
+ xml_array_node(doc, xml_node, child)
76
+ end
77
+ else
78
+ xml_node['value'] = child.to_s unless child.nil?
79
+ end
80
+ end
81
+ end
82
+ end
83
+ xml_node
84
+ end
85
+
86
+ end
87
+ end
88
+ end
89
+ end
@@ -33,9 +33,6 @@ class Tailor
33
33
  on_rparen: :on_lparen
34
34
  }
35
35
 
36
- # Allows for updating the indent expectation for the current line.
37
- attr_accessor :amount_to_change_this
38
-
39
36
  # @return [Fixnum] The actual number of characters the current line is
40
37
  # indented.
41
38
  attr_reader :actual_indentation
@@ -52,7 +49,6 @@ class Tailor
52
49
  @proper = { this_line: 0, next_line: 0 }
53
50
  @actual_indentation = 0
54
51
  @indent_reasons = []
55
- @amount_to_change_this = 0
56
52
 
57
53
  start
58
54
  end
@@ -79,20 +75,12 @@ class Tailor
79
75
  end
80
76
  end
81
77
 
82
- # Sets up expectations in +@proper+ based on the number of +/- reasons
83
- # to change this and next lines, given in +@amount_to_change_this+.
84
- def set_up_line_transition
85
- log "Amount to change this line: #{@amount_to_change_this}"
86
- decrease_this_line if @amount_to_change_this < 0
87
- end
88
-
89
78
  # Should be called just before moving to the next line. This sets the
90
79
  # expectation set in +@proper[:next_line]+ to
91
80
  # +@proper[:this_line]+.
92
81
  def transition_lines
93
82
  if started?
94
83
  log 'Resetting change_this to 0.'
95
- @amount_to_change_this = 0
96
84
  log 'Setting @proper[:this_line] = that of :next_line'
97
85
  @proper[:this_line] = @proper[:next_line]
98
86
  log "Transitioning @proper[:this_line] to #{@proper[:this_line]}"
@@ -286,10 +274,10 @@ class Tailor
286
274
 
287
275
  log "Updated :next after closing; it's now #{@proper[:next_line]}"
288
276
 
289
- meth = "only_#{event_type.to_s.sub('^on_', '')}?"
277
+ meth = "only_#{event_type.to_s.sub(/^on_/, '')}?"
290
278
 
291
279
  if lexed_line.send(meth.to_sym) || lexed_line.to_s =~ /^\s*end\n?$/
292
- @proper[:this_line] = @proper[:this_line] - @spaces
280
+ @proper[:this_line] = [0, @proper[:this_line] - @spaces].max
293
281
  msg = 'End multi-line statement. '
294
282
  msg < "change_this -= 1 -> #{@proper[:this_line]}."
295
283
  log msg
@@ -0,0 +1,58 @@
1
+ require 'ripper'
2
+ require_relative './ast_xml'
3
+
4
+ class Tailor
5
+ module Rulers
6
+ class IndentationSpacesRuler < Tailor::Ruler
7
+
8
+ # Determines whether a line is a continuation of previous lines. For ease
9
+ # of implementation we use the s-exp support in Ripper, rather than trying
10
+ # to work it out solely from the token events on the ruler.
11
+ class LineContinuations
12
+
13
+ include AstXml
14
+
15
+ def initialize(file_name)
16
+ @ast = build_xml(Ripper::SexpBuilder.new(File.read(file_name)).parse)
17
+ end
18
+
19
+ # Is the specified line actually a previous line continuing?
20
+ def line_is_continuation?(lineno)
21
+ @continuations ||= begin
22
+ statements = @ast.xpath('//stmts_add/*[
23
+ preceding-sibling::stmts_new | preceding-sibling::stmts_add]')
24
+ statements.reject do |stmt|
25
+ s = stmt.xpath('ancestor::stmts_add').size
26
+ stmt.xpath("descendant::pos[count(ancestor::stmts_add) = #{s}]/
27
+ @line").map { |s| s.to_s.to_i }.uniq.size < 2
28
+ end.reject { |stmt| stmt.name == 'case' }.map do |stmt|
29
+ lines_nesting = lines_with_nesting_level(stmt)
30
+ lines_nesting.shift
31
+ min_level = lines_nesting.values.min
32
+ lines_nesting.reject { |_, n| n > min_level }.keys
33
+ end.flatten
34
+ end
35
+
36
+ @continuations.include?(lineno)
37
+ end
38
+
39
+ # Are there statements further nested below this line?
40
+ def line_has_nested_statements?(lineno)
41
+ @ast.xpath("//pos[@line='#{lineno}']/
42
+ ancestor::*[parent::stmts_add][1]/
43
+ descendant::stmts_add[descendant::pos[@line > #{lineno}]]").any?
44
+ end
45
+
46
+ private
47
+
48
+ # Returns a hash of line numbers => nesting level
49
+ def lines_with_nesting_level(node)
50
+ Hash[node.xpath('descendant::pos/@line').map do |ln|
51
+ [ln.to_s.to_i, ln.xpath('count(ancestor::stmts_add)').to_i]
52
+ end.sort.uniq]
53
+ end
54
+
55
+ end
56
+ end
57
+ end
58
+ end
@@ -4,7 +4,7 @@ require_relative '../lexer/lexer_constants'
4
4
  class Tailor
5
5
  module Rulers
6
6
  class MaxCodeLinesInClassRuler < Tailor::Ruler
7
- include LexerConstants
7
+ include Tailor::LexerConstants
8
8
 
9
9
  def initialize(config, options)
10
10
  super(config, options)
@@ -14,7 +14,7 @@ class Tailor
14
14
  @end_last_class = false
15
15
  end
16
16
 
17
- def ignored_nl_update(lexed_line, lineno, column)
17
+ def ignored_nl_update(lexed_line, _, _)
18
18
  return if @class_start_lines.empty?
19
19
  return if lexed_line.only_spaces?
20
20
  return if lexed_line.comment_line?
@@ -33,7 +33,7 @@ class Tailor
33
33
  end
34
34
  end
35
35
 
36
- def kw_update(token, lexed_line, lineno, column)
36
+ def kw_update(token, _, lineno, column)
37
37
  if token == 'class' || token == 'module'
38
38
  @class_start_lines << { lineno: lineno, column: column, count: 0 }
39
39
  log "Class start lines: #{@class_start_lines}"
@@ -4,7 +4,7 @@ require_relative '../lexer/lexer_constants'
4
4
  class Tailor
5
5
  module Rulers
6
6
  class MaxCodeLinesInMethodRuler < Tailor::Ruler
7
- include LexerConstants
7
+ include Tailor::LexerConstants
8
8
 
9
9
  def initialize(config, options)
10
10
  super(config, options)
@@ -14,7 +14,7 @@ class Tailor
14
14
  @end_last_method = false
15
15
  end
16
16
 
17
- def ignored_nl_update(lexed_line, lineno, column)
17
+ def ignored_nl_update(lexed_line, _, _)
18
18
  return if @method_start_lines.empty?
19
19
  return if lexed_line.only_spaces?
20
20
  return if lexed_line.comment_line?
@@ -33,7 +33,7 @@ class Tailor
33
33
  end
34
34
  end
35
35
 
36
- def kw_update(token, lexed_line, lineno, column)
36
+ def kw_update(token, _, lineno, column)
37
37
  if token == 'def'
38
38
  @method_start_lines << { lineno: lineno, column: column, count: 0 }
39
39
  log "Method start lines: #{@method_start_lines}"
@@ -14,18 +14,18 @@ class Tailor
14
14
  @comma_columns = []
15
15
  end
16
16
 
17
- def comma_update(text_line, lineno, column)
17
+ def comma_update(_, _, column)
18
18
  @comma_columns << column
19
19
  end
20
20
 
21
- def comment_update(token, lexed_line, file_text, lineno, column)
21
+ def comment_update(token, lexed_line, _, lineno, column)
22
22
  if token =~ /\n$/
23
23
  log 'Found comment with trailing newline.'
24
24
  ignored_nl_update(lexed_line, lineno, column)
25
25
  end
26
26
  end
27
27
 
28
- def ignored_nl_update(lexed_line, lineno, column)
28
+ def ignored_nl_update(lexed_line, lineno, _)
29
29
  check_spaces_after_comma(lexed_line, lineno)
30
30
  end
31
31
 
@@ -9,7 +9,7 @@ class Tailor
9
9
  add_lexer_observers :nl
10
10
  end
11
11
 
12
- def nl_update(current_lexed_line, lineno, column)
12
+ def nl_update(current_lexed_line, lineno, _)
13
13
  measure(current_lexed_line, lineno)
14
14
  end
15
15
 
@@ -19,7 +19,7 @@ class Tailor
19
19
  # @param [Fixnum] lineno Line the problem was found on.
20
20
  def measure(lexed_line, lineno)
21
21
 
22
- idx = lexed_line.index do |pos, token, name|
22
+ idx = lexed_line.index do |_, token, name|
23
23
  token == :on_kw and %w{if unless case}.include?(name)
24
24
  end
25
25
 
@@ -28,7 +28,7 @@ class Tailor
28
28
 
29
29
  if idx
30
30
  column = lexed_line[idx].first.last
31
- pos, token, name = lexed_line[idx + 1]
31
+ pos, token, _ = lexed_line[idx + 1]
32
32
  spaces = case token
33
33
  when :on_lparen then 0
34
34
  when :on_sp
@@ -15,18 +15,18 @@ class Tailor
15
15
  @lbrace_columns = []
16
16
  end
17
17
 
18
- def comment_update(token, lexed_line, file_text, lineno, column)
18
+ def comment_update(token, lexed_line, _, lineno, column)
19
19
  if token =~ /\n$/
20
20
  log 'Found comment with trailing newline.'
21
21
  ignored_nl_update(lexed_line, lineno, column)
22
22
  end
23
23
  end
24
24
 
25
- def ignored_nl_update(lexed_line, lineno, column)
25
+ def ignored_nl_update(lexed_line, lineno, _)
26
26
  check_spaces_after_lbrace(lexed_line, lineno)
27
27
  end
28
28
 
29
- def lbrace_update(lexed_line, lineno, column)
29
+ def lbrace_update(_, _, column)
30
30
  @lbrace_columns << column
31
31
  end
32
32
 
@@ -15,18 +15,18 @@ class Tailor
15
15
  @lbracket_columns = []
16
16
  end
17
17
 
18
- def comment_update(token, lexed_line, file_text, lineno, column)
18
+ def comment_update(token, lexed_line, _, lineno, column)
19
19
  if token =~ /\n$/
20
20
  log 'Found comment with trailing newline.'
21
21
  ignored_nl_update(lexed_line, lineno, column)
22
22
  end
23
23
  end
24
24
 
25
- def ignored_nl_update(lexed_line, lineno, column)
25
+ def ignored_nl_update(lexed_line, lineno, _)
26
26
  check_spaces_after_lbracket(lexed_line, lineno)
27
27
  end
28
28
 
29
- def lbracket_update(lexed_line, lineno, column)
29
+ def lbracket_update(_, _, column)
30
30
  @lbracket_columns << column
31
31
  end
32
32
 
@@ -9,18 +9,18 @@ class Tailor
9
9
  @lparen_columns = []
10
10
  end
11
11
 
12
- def comment_update(token, lexed_line, file_text, lineno, column)
12
+ def comment_update(token, lexed_line, _, lineno, column)
13
13
  if token =~ /\n$/
14
14
  log 'Found comment with trailing newline.'
15
15
  ignored_nl_update(lexed_line, lineno, column)
16
16
  end
17
17
  end
18
18
 
19
- def ignored_nl_update(lexed_line, lineno, column)
19
+ def ignored_nl_update(lexed_line, lineno, _)
20
20
  check_spaces_after_lparen(lexed_line, lineno)
21
21
  end
22
22
 
23
- def lparen_update(lineno, column)
23
+ def lparen_update(_, column)
24
24
  @lparen_columns << column
25
25
  end
26
26
 
@@ -11,11 +11,11 @@ class Tailor
11
11
  @comma_columns = []
12
12
  end
13
13
 
14
- def comma_update(text_line, lineno, column)
14
+ def comma_update(_, _, column)
15
15
  @comma_columns << column
16
16
  end
17
17
 
18
- def comment_update(token, lexed_line, file_text, lineno, column)
18
+ def comment_update(token, lexed_line, _, lineno, column)
19
19
  if token =~ /\n$/
20
20
  log 'Found comment with trailing newline.'
21
21
  ignored_nl_update(lexed_line, lineno, column)
@@ -59,7 +59,7 @@ class Tailor
59
59
  @comma_columns.clear
60
60
  end
61
61
 
62
- def ignored_nl_update(lexed_line, lineno, column)
62
+ def ignored_nl_update(lexed_line, lineno, _)
63
63
  check_spaces_before_comma(lexed_line, lineno)
64
64
  end
65
65
 
@@ -48,13 +48,13 @@ class Tailor
48
48
  previous_event.last.size
49
49
  end
50
50
 
51
- def embexpr_beg_update(lexed_line, lineno, column)
51
+ def embexpr_beg_update(_, _, _)
52
52
  if RUBY_VERSION < '2.0.0'
53
53
  @lbrace_nesting << :embexpr_beg
54
54
  end
55
55
  end
56
56
 
57
- def lbrace_update(lexed_line, lineno, column)
57
+ def lbrace_update(_, _, _)
58
58
  @lbrace_nesting << :lbrace
59
59
  end
60
60
 
@@ -40,11 +40,11 @@ class Tailor
40
40
  end
41
41
  end
42
42
 
43
- def embexpr_beg_update(lexed_line, lineno, column)
43
+ def embexpr_beg_update(_, _, _)
44
44
  @lbrace_nesting << :embexpr_beg
45
45
  end
46
46
 
47
- def lbrace_update(lexed_line, lineno, column)
47
+ def lbrace_update(_, _, _)
48
48
  @lbrace_nesting << :lbrace
49
49
  end
50
50
 
@@ -12,6 +12,17 @@
12
12
  # indentation_spaces The number of spaces to consider a proper indent.
13
13
  # Default: 2
14
14
  #
15
+ # Option: line_continuations
16
+ # Indicates that a statement that spans multiple lines
17
+ # should indent the second and subsequent lines. Ex.:
18
+ # style.indentation_spaces 2, level: :error, line_continuations: true
19
+ #
20
+ # Option: argument_align
21
+ # Allowing you to specify that method declarations and
22
+ # calls should indent to the first argument on
23
+ # subsequent lines. Ex.:
24
+ # style.indentation_spaces 2, level: :error, argument_alignment: true
25
+ #
15
26
  # max_line_length The maximum number of characters in a line before
16
27
  # tailor complains.
17
28
  # Default: 80
@@ -79,6 +90,23 @@
79
90
  # the file.
80
91
  # Default: 1
81
92
  #
93
+ #------------------------------------------------------------------------------
94
+ # Common Syntax
95
+ #------------------------------------------------------------------------------
96
+ # allow_conditional_parentheses
97
+ # Checks to see if a conditional is unnecessarily
98
+ # wrapped in parentheses.
99
+ # Default: true
100
+ #
101
+ # allow_unnecessary_double_quotes
102
+ # Checks for use of double-quotes when no interpolation
103
+ # is used.
104
+ # Default: false
105
+ #
106
+ # allow_unnecessary_interpolation
107
+ # Checks for unnecessary interpolation in strings.
108
+ # Default: false
109
+ #
82
110
  Tailor.config do |config|
83
111
  config.formatters "<%= formatters.join(', ') %>"
84
112
  config.file_set '<%= file_list %>' do |style|<% style.each do |rule, value| %>