tailor 1.0.0.alpha2 → 1.0.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 (84) hide show
  1. data/.gitignore +1 -0
  2. data/.tailor +10 -2
  3. data/Gemfile.lock +2 -2
  4. data/History.rdoc +20 -0
  5. data/README.rdoc +176 -26
  6. data/features/configurable.feature +19 -39
  7. data/features/horizontal_spacing.feature +3 -2
  8. data/features/indentation.feature +2 -2
  9. data/features/indentation/bad_files_with_no_trailing_newline.feature +9 -8
  10. data/features/indentation/good_files_with_no_trailing_newline.feature +19 -6
  11. data/features/name_detection.feature +2 -2
  12. data/features/support/env.rb +0 -2
  13. data/features/support/file_cases/horizontal_spacing_cases.rb +5 -4
  14. data/features/support/file_cases/indentation_cases.rb +105 -54
  15. data/features/support/file_cases/naming_cases.rb +0 -1
  16. data/features/support/file_cases/vertical_spacing_cases.rb +0 -1
  17. data/features/support/legacy/bad_ternary_colon_spacing.rb +1 -1
  18. data/features/valid_ruby.feature +17 -0
  19. data/features/vertical_spacing.feature +40 -19
  20. data/lib/ext/string_ext.rb +12 -0
  21. data/lib/tailor/cli.rb +7 -5
  22. data/lib/tailor/cli/options.rb +13 -3
  23. data/lib/tailor/composite_observable.rb +17 -2
  24. data/lib/tailor/configuration.rb +83 -72
  25. data/lib/tailor/configuration/style.rb +85 -0
  26. data/lib/tailor/critic.rb +67 -117
  27. data/lib/tailor/formatter.rb +38 -0
  28. data/lib/tailor/formatters/text.rb +35 -10
  29. data/lib/tailor/lexed_line.rb +38 -5
  30. data/lib/tailor/lexer.rb +150 -14
  31. data/lib/tailor/{lexer_constants.rb → lexer/lexer_constants.rb} +9 -7
  32. data/lib/tailor/lexer/token.rb +6 -2
  33. data/lib/tailor/logger.rb +4 -0
  34. data/lib/tailor/problem.rb +8 -73
  35. data/lib/tailor/reporter.rb +1 -1
  36. data/lib/tailor/ruler.rb +67 -6
  37. data/lib/tailor/rulers/allow_camel_case_methods_ruler.rb +9 -1
  38. data/lib/tailor/rulers/allow_hard_tabs_ruler.rb +9 -1
  39. data/lib/tailor/rulers/allow_invalid_ruby_ruler.rb +38 -0
  40. data/lib/tailor/rulers/allow_screaming_snake_case_classes_ruler.rb +9 -2
  41. data/lib/tailor/rulers/allow_trailing_line_spaces_ruler.rb +10 -5
  42. data/lib/tailor/rulers/indentation_spaces_ruler.rb +93 -26
  43. data/lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb +128 -84
  44. data/lib/tailor/rulers/max_code_lines_in_class_ruler.rb +9 -5
  45. data/lib/tailor/rulers/max_code_lines_in_method_ruler.rb +9 -5
  46. data/lib/tailor/rulers/max_line_length_ruler.rb +10 -5
  47. data/lib/tailor/rulers/spaces_after_comma_ruler.rb +13 -4
  48. data/lib/tailor/rulers/spaces_after_lbrace_ruler.rb +8 -4
  49. data/lib/tailor/rulers/spaces_after_lbracket_ruler.rb +8 -4
  50. data/lib/tailor/rulers/spaces_after_lparen_ruler.rb +8 -4
  51. data/lib/tailor/rulers/spaces_before_comma_ruler.rb +8 -4
  52. data/lib/tailor/rulers/spaces_before_lbrace_ruler.rb +13 -6
  53. data/lib/tailor/rulers/spaces_before_rbrace_ruler.rb +12 -8
  54. data/lib/tailor/rulers/spaces_before_rbracket_ruler.rb +12 -5
  55. data/lib/tailor/rulers/spaces_before_rparen_ruler.rb +13 -6
  56. data/lib/tailor/rulers/spaces_in_empty_braces_ruler.rb +13 -9
  57. data/lib/tailor/rulers/trailing_newlines_ruler.rb +10 -5
  58. data/lib/tailor/tailorrc.erb +3 -3
  59. data/lib/tailor/version.rb +1 -1
  60. data/m.rb +15 -0
  61. data/spec/spec_helper.rb +0 -1
  62. data/spec/tailor/cli_spec.rb +8 -9
  63. data/spec/tailor/composite_observable_spec.rb +41 -0
  64. data/spec/tailor/configuration/style_spec.rb +197 -0
  65. data/spec/tailor/configuration_spec.rb +52 -33
  66. data/spec/tailor/critic_spec.rb +7 -8
  67. data/spec/tailor/formatter_spec.rb +52 -0
  68. data/spec/tailor/lexed_line_spec.rb +236 -88
  69. data/spec/tailor/lexer_spec.rb +8 -63
  70. data/spec/tailor/problem_spec.rb +14 -46
  71. data/spec/tailor/reporter_spec.rb +8 -8
  72. data/spec/tailor/ruler_spec.rb +1 -1
  73. data/spec/tailor/rulers/indentation_spaces_ruler/indentation_manager_spec.rb +132 -176
  74. data/spec/tailor/rulers/indentation_spaces_ruler_spec.rb +41 -33
  75. data/spec/tailor/rulers/{spaces_after_comma_spec.rb → spaces_after_comma_ruler_spec.rb} +5 -5
  76. data/spec/tailor/rulers/spaces_after_lbrace_ruler_spec.rb +14 -14
  77. data/spec/tailor/rulers/spaces_before_lbrace_ruler_spec.rb +1 -1
  78. data/spec/tailor/rulers/spaces_before_rbrace_ruler_spec.rb +1 -1
  79. data/spec/tailor/version_spec.rb +1 -1
  80. data/spec/tailor_spec.rb +3 -1
  81. data/tailor.gemspec +11 -3
  82. data/uest.rb +9 -0
  83. metadata +66 -41
  84. data/features/step_definitions/spacing/commas_steps.rb +0 -14
@@ -6,9 +6,27 @@ require_relative 'indentation_spaces_ruler/indentation_manager'
6
6
  class Tailor
7
7
  module Rulers
8
8
  class IndentationSpacesRuler < Tailor::Ruler
9
- def initialize(config)
10
- super(config)
9
+ def initialize(config, options)
10
+ super(config, options)
11
+ add_lexer_observers(
12
+ :comment,
13
+ :embexpr_beg,
14
+ :embexpr_end,
15
+ :ignored_nl,
16
+ :kw,
17
+ :lbrace,
18
+ :lbracket,
19
+ :lparen,
20
+ :nl,
21
+ :rbrace,
22
+ :rbracket,
23
+ :rparen,
24
+ :tstring_beg,
25
+ :tstring_end
26
+ )
11
27
  @manager = IndentationManager.new(@config)
28
+ @embexpr_beg = false
29
+ @tstring_nesting = []
12
30
  end
13
31
 
14
32
  def comment_update(token, lexed_line, file_text, lineno, column)
@@ -32,16 +50,18 @@ class Tailor
32
50
  end
33
51
 
34
52
  def embexpr_beg_update
35
- @manager.embexpr_beg = true
53
+ @embexpr_beg = true
36
54
  end
37
55
 
56
+ # Due to a Ripper bug (depending on which Ruby version you have), this may
57
+ # or may not get triggered.
58
+ # More info: https://bugs.ruby-lang.org/issues/6211
38
59
  def embexpr_end_update
39
- @manager.embexpr_beg = false
60
+ @embexpr_beg = false
40
61
  end
41
62
 
42
63
  def ignored_nl_update(current_lexed_line, lineno, column)
43
64
  log "indent reasons on entry: #{@manager.indent_reasons}"
44
- stop if @manager.tstring_nesting.size > 0
45
65
 
46
66
  if current_lexed_line.only_spaces?
47
67
  log "Line of only spaces. Moving on."
@@ -52,13 +72,17 @@ class Tailor
52
72
  if @manager.line_ends_with_single_token_indenter?(current_lexed_line)
53
73
  log "Line ends with single-token indent token."
54
74
 
55
- unless @manager.comma_is_part_of_enclosed_statement?(current_lexed_line, lineno)
75
+ unless @manager.in_an_enclosure? &&
76
+ current_lexed_line.ends_with_comma?
56
77
  log "Line-ending single-token indenter found."
57
78
  token_event = current_lexed_line.last_non_line_feed_event
58
79
 
59
80
  unless @manager.line_ends_with_same_as_last token_event
60
- log "Line ends with different type of single-token indenter: #{token_event}"
61
- @manager.add_indent_reason(token_event[1], token_event.last, lineno)
81
+ msg = "Line ends with different type of single-token "
82
+ msg << "indenter: #{token_event}"
83
+ log msg
84
+ @manager.add_indent_reason(token_event[1], token_event.last,
85
+ lineno)
62
86
  end
63
87
  end
64
88
  end
@@ -73,8 +97,13 @@ class Tailor
73
97
  end
74
98
 
75
99
  def kw_update(token, lexed_line, lineno, column)
100
+ if lexed_line.keyword_is_symbol?
101
+ log "Keyword is prefaced by a :, indicating it's really a Symbol."
102
+ return
103
+ end
104
+
76
105
  if token == "end"
77
- @manager.update_for_closing_reason(:on_kw, lexed_line, lineno)
106
+ @manager.update_for_closing_reason(:on_kw, lexed_line)
78
107
  return
79
108
  end
80
109
 
@@ -117,8 +146,8 @@ class Tailor
117
146
  log "last indent reason type: #{@manager.last_indent_reason_type}"
118
147
  log "I think this is a single-token closing line..."
119
148
 
120
- @manager.update_for_closing_reason(@manager.indent_reasons.last[:event_type],
121
- current_lexed_line, lineno)
149
+ @manager.update_for_closing_reason(@manager.indent_reasons.
150
+ last[:event_type], current_lexed_line)
122
151
  end
123
152
 
124
153
  @manager.set_up_line_transition
@@ -131,17 +160,37 @@ class Tailor
131
160
  @manager.transition_lines
132
161
  end
133
162
 
163
+ # Since Ripper parses the } in a #{} as :on_rbrace instead of
164
+ # :on_embexpr_end, this works around that by using +@embexpr_beg to track
165
+ # the state of that event. As such, this should only be called from
166
+ # #rbrace_update.
167
+ #
168
+ # @return [Boolean]
169
+ def in_embexpr?
170
+ @embexpr_beg == true
171
+ end
172
+
134
173
  def rbrace_update(current_lexed_line, lineno, column)
174
+ if in_embexpr?
175
+ msg = "Got :rbrace and @embexpr_beg is true. "
176
+ msg << " Must be at an @embexpr_end."
177
+ log msg
178
+ @embexpr_beg = false
179
+ return
180
+ end
181
+
135
182
  if @manager.multi_line_braces?(lineno)
136
183
  log "End of multi-line braces!"
137
184
 
138
185
  if current_lexed_line.only_rbrace?
139
186
  @manager.amount_to_change_this -= 1
140
- log "lonely rbrace. change_this -= 1 -> #{@manager.amount_to_change_this}"
187
+ msg = "lonely rbrace. "
188
+ msg << "change_this -= 1 -> #{@manager.amount_to_change_this}"
189
+ log msg
141
190
  end
142
191
  end
143
192
 
144
- @manager.update_for_closing_reason(:on_rbrace, current_lexed_line, lineno)
193
+ @manager.update_for_closing_reason(:on_rbrace, current_lexed_line)
145
194
  end
146
195
 
147
196
  def rbracket_update(current_lexed_line, lineno, column)
@@ -150,11 +199,13 @@ class Tailor
150
199
 
151
200
  if current_lexed_line.only_rbracket?
152
201
  @manager.amount_to_change_this -= 1
153
- log "lonely rbracket. change_this -= 1 -> #{@manager.amount_to_change_this}"
202
+ msg = "lonely rbracket. "
203
+ msg << "change_this -= 1 -> #{@manager.amount_to_change_this}"
204
+ log msg
154
205
  end
155
206
  end
156
207
 
157
- @manager.update_for_closing_reason(:on_rbracket, current_lexed_line, lineno)
208
+ @manager.update_for_closing_reason(:on_rbracket, current_lexed_line)
158
209
  end
159
210
 
160
211
  def rparen_update(current_lexed_line, lineno, column)
@@ -163,21 +214,36 @@ class Tailor
163
214
 
164
215
  if current_lexed_line.only_rparen?
165
216
  @manager.amount_to_change_this -= 1
166
- log "lonely rparen. change_this -= 1 -> #{@manager.amount_to_change_this}"
217
+ msg = "lonely rparen. "
218
+ msg << "change_this -= 1 -> #{@manager.amount_to_change_this}"
219
+ log msg
167
220
  end
168
221
  end
169
222
 
170
- @manager.update_for_closing_reason(:on_rparen, current_lexed_line, lineno)
223
+ @manager.update_for_closing_reason(:on_rparen, current_lexed_line)
171
224
  end
172
225
 
173
- def tstring_beg_update(lineno)
174
- @manager.tstring_nesting << lineno
226
+ def tstring_beg_update(lexed_line, lineno)
227
+ @tstring_nesting << lineno
228
+ @manager.update_actual_indentation(lexed_line)
229
+ log "tstring_nesting is now: #{@tstring_nesting}"
175
230
  @manager.stop
176
231
  end
177
232
 
178
- def tstring_end_update
179
- @manager.tstring_nesting.pop
180
- @manager.start unless @manager.in_tstring?
233
+ def tstring_end_update(current_line)
234
+ unless @tstring_nesting.empty?
235
+ tstring_start_line = @tstring_nesting.pop
236
+
237
+ if tstring_start_line < current_line
238
+ measure(tstring_start_line, @manager.actual_indentation)
239
+ end
240
+ end
241
+
242
+ @manager.start unless in_tstring?
243
+ end
244
+
245
+ def in_tstring?
246
+ !@tstring_nesting.empty?
181
247
  end
182
248
 
183
249
  # Checks if the line's indentation level is appropriate.
@@ -188,10 +254,11 @@ class Tailor
188
254
  log "Measuring..."
189
255
 
190
256
  if @manager.actual_indentation != @manager.should_be_at
191
- @problems << Problem.new(:indentation, lineno, column,
192
- { actual_indentation: @manager.actual_indentation,
193
- should_be_at: @manager.should_be_at }
194
- )
257
+ msg = "Line is indented to column #{@manager.actual_indentation}, "
258
+ msg << "but should be at #{@manager.should_be_at}."
259
+
260
+ @problems << Problem.new(problem_type, lineno,
261
+ @manager.actual_indentation, msg, @options[:level])
195
262
  end
196
263
  end
197
264
  end
@@ -1,14 +1,30 @@
1
1
  require_relative '../../ruler'
2
2
  require_relative '../../logger'
3
- require_relative '../../lexer_constants'
3
+ require_relative '../../lexer/lexer_constants'
4
4
 
5
5
  class Tailor
6
6
  module Rulers
7
7
  class IndentationSpacesRuler < Tailor::Ruler
8
+
9
+ # Used for managing the state of indentation for some file/text. An
10
+ # object of this type has no knowledge of the file/text itself, but rather
11
+ # just manages indentation expectations based on the object's user's
12
+ # input, somewhat like a state machine.
13
+ #
14
+ # For the sake of talking about indentation expectations, the docs here
15
+ # make mention of 'levels' of indentation. A _level_ here is simply
16
+ # 1 * (number of spaces to indent); so if you've set (number of spaces to
17
+ # indent) to 2, saying something should be indented 1 level, is simply
18
+ # saying that it should be indented 2 spaces.
8
19
  class IndentationManager
9
20
  include Tailor::LexerConstants
10
21
  include Tailor::Logger::Mixin
11
22
 
23
+ # These are event names generated by the {Lexer} that signify
24
+ # indentation level should/could increase by 1.
25
+ ENCLOSERS = Set.new [:on_lbrace, :on_lbracket, :on_lparen]
26
+
27
+ # Look-up table that allows for <tt>OPEN_EVENT_FOR[:on_rbrace]</tt>.
12
28
  OPEN_EVENT_FOR = {
13
29
  on_kw: :on_kw,
14
30
  on_rbrace: :on_lbrace,
@@ -16,25 +32,27 @@ class Tailor
16
32
  on_rparen: :on_lparen
17
33
  }
18
34
 
19
- attr_accessor :amount_to_change_next
35
+ # Allows for updating the indent expectation for the current line.
20
36
  attr_accessor :amount_to_change_this
21
- attr_accessor :embexpr_beg
22
37
 
38
+ # @return [Fixnum] The actual number of characters the current line is
39
+ # indented.
23
40
  attr_reader :actual_indentation
41
+
42
+ # @return [Array<Hash>] Each element represents a reason why code should
43
+ # be indented. Indent levels are not necessarily 1:1 relationship
44
+ # to these reasons (hence the need for this class).
24
45
  attr_reader :indent_reasons
25
- attr_reader :tstring_nesting
26
46
 
47
+ # @param [Fixnum] spaces The number of spaces each level of indentation
48
+ # should move in & out.
27
49
  def initialize(spaces)
28
50
  @spaces = spaces
29
-
30
- log "Setting @proper[:this_line] to 0."
31
51
  @proper = { this_line: 0, next_line: 0 }
32
52
  @actual_indentation = 0
33
-
34
53
  @indent_reasons = []
35
- @tstring_nesting = []
36
-
37
54
  @amount_to_change_this = 0
55
+
38
56
  start
39
57
  end
40
58
 
@@ -44,7 +62,7 @@ class Tailor
44
62
  end
45
63
 
46
64
  # Decreases the indentation expectation for the current line by
47
- # +@spaces+.
65
+ # 1 level.
48
66
  def decrease_this_line
49
67
  if started?
50
68
  @proper[:this_line] -= @spaces
@@ -60,10 +78,8 @@ class Tailor
60
78
  end
61
79
  end
62
80
 
63
-
64
81
  # Sets up expectations in +@proper+ based on the number of +/- reasons
65
- # to change this and next lines, given in +@amount_to_change_this+ and
66
- # +@amount_to_change_next+, respectively.
82
+ # to change this and next lines, given in +@amount_to_change_this+.
67
83
  def set_up_line_transition
68
84
  log "Amount to change this line: #{@amount_to_change_this}"
69
85
  decrease_this_line if @amount_to_change_this < 0
@@ -133,6 +149,7 @@ class Tailor
133
149
  lexed_line.ends_with_op? ||
134
150
  lexed_line.ends_with_comma? ||
135
151
  lexed_line.ends_with_period? ||
152
+ lexed_line.ends_with_label? ||
136
153
  lexed_line.ends_with_modifier_kw?
137
154
  end
138
155
 
@@ -148,61 +165,37 @@ class Tailor
148
165
  @indent_reasons.last[:event_type] == token_event[1]
149
166
  end
150
167
 
151
- # Checks to see if +lexed_line+ ends with a comma, and if it is in the
152
- # middle of an enclosed statement (unclosed braces, brackets, parens).
153
- # You don't want to update indentation expectations for this comma if
154
- # you've already done so for the start of the enclosed statement.
168
+ # Determines if the current spot in the file is enclosed in braces,
169
+ # brackets, or parens.
155
170
  #
156
- # @param [LexedLine] lexed_line
157
- # @param [Fixnum] lineno
158
171
  # @return [Boolean]
159
- def comma_is_part_of_enclosed_statement?(lexed_line, lineno)
172
+ def in_an_enclosure?
160
173
  return false if @indent_reasons.empty?
161
174
 
162
- lexed_line.ends_with_comma? &&
163
- @indent_reasons.last[:event_type] == :on_lbrace ||
164
- @indent_reasons.last[:event_type] == :on_lbracket ||
165
- @indent_reasons.last[:event_type] == :on_lparen
166
- end
167
-
168
- # Checks if indentation level got increased on this line because of a
169
- # keyword and if it got increased on this line because of a
170
- # single-token indenter.
171
- #
172
- # @param [Fixnum] lineno
173
- # @return [Boolean]
174
- def keyword_and_single_token_line?(lineno)
175
- d_tokens = @indent_reasons.find_all { |t| t[:lineno] == lineno }
176
- return false if d_tokens.empty?
175
+ i_reasons = @indent_reasons.dup
176
+ log "i reasons: #{i_reasons}"
177
177
 
178
- kw_token = d_tokens.reverse.find do |t|
179
- ['{', '[', '('].none? { |e| t[:token] == e }
178
+ until ENCLOSERS.include? i_reasons.last[:event_type]
179
+ i_reasons.pop
180
+ break if i_reasons.empty?
180
181
  end
181
182
 
182
- return false if kw_token.nil?
183
+ return false if i_reasons.empty?
183
184
 
184
- s_token = @indent_reasons.reverse.find { |t| t[:lineno] == lineno }
185
- return false unless s_token
186
-
187
- true
188
- end
189
-
190
- def single_token_start_line
191
- return if @indent_reasons.empty?
192
-
193
- @indent_reasons.first[:lineno]
194
- end
195
-
196
- def double_token_start_line
197
- return if @indent_reasons.empty?
198
-
199
- @indent_reasons.last[:lineno]
200
- end
201
-
202
- def double_tokens_in_line(lineno)
203
- @indent_reasons.find_all { |t| t[:lineno] == lineno}
185
+ i_reasons.last[:event_type] == :on_lbrace ||
186
+ i_reasons.last[:event_type] == :on_lbracket ||
187
+ i_reasons.last[:event_type] == :on_lparen
204
188
  end
205
189
 
190
+ # Adds to the list of reasons to indent the next line, then increases
191
+ # the expectation for the next line by +@spaces+.
192
+ #
193
+ # @param [Symbol] event_type The event type that caused the reason for
194
+ # indenting.
195
+ # @param [Tailor::Token,String] token The token that caused the reason
196
+ # for indenting.
197
+ # @param [Fixnum] lineno The line number the reason for indenting was
198
+ # discovered on.
206
199
  def add_indent_reason(event_type, token, lineno)
207
200
  @indent_reasons << {
208
201
  event_type: event_type,
@@ -216,6 +209,14 @@ class Tailor
216
209
  @indent_reasons.each { |r| log r.to_s }
217
210
  end
218
211
 
212
+ # An "opening reason" is a reason for indenting that also has a "closing
213
+ # reason", such as a +def+, +{+, +[+, +(+.
214
+ #
215
+ # @param [Symbol] event_type The event type that is the opening reason.
216
+ # @param [Tailor::Token,String] token The token that is the opening
217
+ # reasons.
218
+ # @param [Fixnum] lineno The line number the opening reason was found
219
+ # on.
219
220
  def update_for_opening_reason(event_type, token, lineno)
220
221
  if token.modifier_keyword?
221
222
  log "Found modifier in line: '#{token}'"
@@ -232,6 +233,14 @@ class Tailor
232
233
  add_indent_reason(event_type, token, lineno)
233
234
  end
234
235
 
236
+ # A "continuation reason" is a reason for indenting & outdenting that's
237
+ # not an opening or closing reason, such as +elsif+, +rescue+, +when+
238
+ # (in a +case+ statement), etc.
239
+ #
240
+ # @param [Symbol] event_type The event type that is the opening reason.
241
+ # @param [Tailor::LexedLine] lexed_line
242
+ # @param [Fixnum] lineno The line number the opening reason was found
243
+ # on.
235
244
  def update_for_continuation_reason(token, lexed_line, lineno)
236
245
  d_tokens = @indent_reasons.dup
237
246
  d_tokens.pop
@@ -247,22 +256,24 @@ class Tailor
247
256
 
248
257
  last_reason_line = @indent_reasons.find { |r| r[:lineno] == lineno }
249
258
 
250
- if last_reason_line.nil?
251
- @proper[:next_line] = @indent_reasons.last[:should_be_at] + @spaces
259
+ @proper[:next_line] = if last_reason_line.nil?
260
+ if @indent_reasons.empty?
261
+ @spaces
262
+ else
263
+ @indent_reasons.last[:should_be_at] + @spaces
264
+ end
252
265
  else
253
- @proper[:next_line] = @indent_reasons.last[:should_be_at] - @spaces
266
+ @indent_reasons.last[:should_be_at] - @spaces
254
267
  end
255
268
  end
256
269
 
257
- def update_for_closing_reason(event_type, lexed_line, lineno)
258
- if event_type == :on_rbrace && @embexpr_beg == true
259
- msg = "Got :rbrace and @embexpr_beg is true. "
260
- msg << " Must be at an @embexpr_end."
261
- log msg
262
- @embexpr_beg = false
263
- return
264
- end
265
-
270
+ # A "closing reason" is a reason for indenting that also has an "opening
271
+ # reason", such as a +end+, +}+, +]+, +)+.
272
+ #
273
+ # @param [Symbol] event_type The event type that is the closing reason.
274
+ # @param [Tailor::Token,String] token The token that is the closing
275
+ # reason.
276
+ def update_for_closing_reason(event_type, lexed_line)
266
277
  remove_continuation_keywords
267
278
  remove_appropriate_reason(event_type)
268
279
 
@@ -284,12 +295,14 @@ class Tailor
284
295
  end
285
296
  end
286
297
 
287
- def remove_appropriate_reason(event_type)
288
- last_opening_event = @indent_reasons.reverse.find do |r|
289
- r[:event_type] == OPEN_EVENT_FOR[event_type]
290
- end
291
-
292
- if last_opening_event
298
+ # Removes the last matching opening reason reason of +event_type+ from
299
+ # the list of indent reasons.
300
+ #
301
+ # @param [Symbol] closing_event_type The closing event for which to find
302
+ # the matching opening event to remove from the list of indent
303
+ # reasons.
304
+ def remove_appropriate_reason(closing_event_type)
305
+ if last_opening_event = last_opening_event(closing_event_type)
293
306
  r_index = @indent_reasons.reverse.index(last_opening_event)
294
307
  index = @indent_reasons.size - r_index - 1
295
308
  tmp_reasons = []
@@ -299,30 +312,64 @@ class Tailor
299
312
  end
300
313
 
301
314
  @indent_reasons.replace(tmp_reasons)
315
+ elsif last_single_token_event
316
+ log "Just popped off reason: #{@indent_reasons.pop}"
302
317
  else
303
- @indent_reasons.pop
318
+ log "Couldn't find a matching opening reason to pop off...'"
319
+ return
304
320
  end
305
321
 
306
322
  log "Removed indent reason; it's now:"
307
323
  @indent_reasons.each { |r| log r.to_s }
308
324
  end
309
325
 
326
+ # A "single-token" event is one that that causes indentation
327
+ # expectations to increase. They don't have have a paired closing
328
+ # reason like opening reasons. Instead, they're determined to be done
329
+ # with their indenting when an :on_ignored_nl occurs. Single-token
330
+ # events are operators and commas (commas that aren't used as
331
+ # separators in {, [, ( events).
332
+ def last_single_token_event
333
+ return nil if @indent_reasons.empty?
334
+
335
+ @indent_reasons.reverse.find do |r|
336
+ !ENCLOSERS.include? r[:event_type] &&
337
+ r[:event_type] != :on_kw
338
+ end
339
+ end
340
+
341
+
342
+ # Returns the last matching opening event that corresponds to the
343
+ # +closing_event_type+.
344
+ #
345
+ # @param [Symbol] closing_event_type The closing event for which to
346
+ # find its associated opening event.
347
+ def last_opening_event(closing_event_type)
348
+ return nil if @indent_reasons.empty?
349
+
350
+ @indent_reasons.reverse.find do |r|
351
+ r[:event_type] == OPEN_EVENT_FOR[closing_event_type]
352
+ end
353
+ end
354
+
310
355
  def last_indent_reason_type
311
356
  return if @indent_reasons.empty?
312
357
 
313
358
  @indent_reasons.last[:event_type]
314
359
  end
315
360
 
361
+ # Removes all continuation keywords from the list of
362
+ # indentation reasons.
316
363
  def remove_continuation_keywords
317
364
  return if @indent_reasons.empty?
318
365
 
319
366
  while CONTINUATION_KEYWORDS.include?(@indent_reasons.last[:token])
320
- @indent_reasons.pop
367
+ log "Just popped off continuation reason: #{@indent_reasons.pop}"
321
368
  end
322
369
  end
323
370
 
324
- # Overriding to be able to call #multi_line_brackets?,
325
- # #multi_line_braces?, and #multi_line_parens?, where each takes a
371
+ # Overriding to be able to call +#multi_line_brackets?+,
372
+ # +#multi_line_braces?+, and +#multi_line_parens?+, where each takes a
326
373
  # single parameter, which is the lineno.
327
374
  #
328
375
  # @return [Boolean]
@@ -342,6 +389,7 @@ class Tailor
342
389
  t[:token] == token
343
390
  end
344
391
 
392
+ log "#{meth} called, but no #{$1} were found." if tokens.empty?
345
393
  return false if tokens.empty?
346
394
 
347
395
  token_on_this_line = tokens.find { |t| t[:lineno] == lineno }
@@ -352,10 +400,6 @@ class Tailor
352
400
  super(meth, *args, &blk)
353
401
  end
354
402
  end
355
-
356
- def in_tstring?
357
- !@tstring_nesting.empty?
358
- end
359
403
  end
360
404
  end
361
405
  end