tailor 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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| %>