tailor 0.1.5 → 1.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. data/.gitignore +9 -1
  2. data/.rspec +2 -1
  3. data/.tailor +6 -0
  4. data/Gemfile.lock +47 -78
  5. data/{ChangeLog.rdoc → History.rdoc} +0 -0
  6. data/README.rdoc +157 -24
  7. data/Rakefile +0 -9
  8. data/bin/tailor +16 -69
  9. data/features/configurable.feature +78 -0
  10. data/features/horizontal_spacing.feature +262 -0
  11. data/features/indentation.feature +17 -21
  12. data/features/indentation/bad_files_with_no_trailing_newline.feature +90 -0
  13. data/features/indentation/good_files_with_no_trailing_newline.feature +206 -0
  14. data/features/name_detection.feature +72 -0
  15. data/features/step_definitions/indentation_steps.rb +10 -133
  16. data/features/support/env.rb +7 -15
  17. data/features/support/file_cases/horizontal_spacing_cases.rb +265 -0
  18. data/features/support/file_cases/indentation_cases.rb +972 -0
  19. data/features/support/file_cases/naming_cases.rb +52 -0
  20. data/features/support/file_cases/vertical_spacing_cases.rb +70 -0
  21. data/features/support/hooks.rb +8 -0
  22. data/features/support/{1_file_with_bad_operator_spacing → legacy}/bad_op_spacing.rb +0 -0
  23. data/features/support/{1_file_with_bad_ternary_colon_spacing → legacy}/bad_ternary_colon_spacing.rb +0 -0
  24. data/features/support/{1_long_file_with_indentation/my_project.rb → legacy/long_file_with_indentation.rb} +1 -1
  25. data/features/support/world.rb +14 -0
  26. data/features/vertical_spacing.feature +114 -0
  27. data/lib/ext/string_ext.rb +5 -0
  28. data/lib/tailor.rb +6 -252
  29. data/lib/tailor/cli.rb +49 -0
  30. data/lib/tailor/cli/options.rb +251 -0
  31. data/lib/tailor/composite_observable.rb +56 -0
  32. data/lib/tailor/configuration.rb +263 -0
  33. data/lib/tailor/critic.rb +162 -0
  34. data/lib/tailor/formatters/text.rb +126 -0
  35. data/lib/tailor/lexed_line.rb +246 -0
  36. data/lib/tailor/lexer.rb +428 -0
  37. data/lib/tailor/lexer/token.rb +103 -0
  38. data/lib/tailor/lexer_constants.rb +75 -0
  39. data/lib/tailor/logger.rb +28 -0
  40. data/lib/tailor/problem.rb +100 -0
  41. data/lib/tailor/reporter.rb +48 -0
  42. data/lib/tailor/ruler.rb +39 -0
  43. data/lib/tailor/rulers.rb +7 -0
  44. data/lib/tailor/rulers/allow_camel_case_methods_ruler.rb +30 -0
  45. data/lib/tailor/rulers/allow_hard_tabs_ruler.rb +22 -0
  46. data/lib/tailor/rulers/allow_screaming_snake_case_classes_ruler.rb +32 -0
  47. data/lib/tailor/rulers/allow_trailing_line_spaces_ruler.rb +33 -0
  48. data/lib/tailor/rulers/indentation_spaces_ruler.rb +199 -0
  49. data/lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb +362 -0
  50. data/lib/tailor/rulers/max_code_lines_in_class_ruler.rb +84 -0
  51. data/lib/tailor/rulers/max_code_lines_in_method_ruler.rb +84 -0
  52. data/lib/tailor/rulers/max_line_length_ruler.rb +31 -0
  53. data/lib/tailor/rulers/spaces_after_comma_ruler.rb +83 -0
  54. data/lib/tailor/rulers/spaces_after_lbrace_ruler.rb +114 -0
  55. data/lib/tailor/rulers/spaces_after_lbracket_ruler.rb +123 -0
  56. data/lib/tailor/rulers/spaces_after_lparen_ruler.rb +116 -0
  57. data/lib/tailor/rulers/spaces_before_comma_ruler.rb +67 -0
  58. data/lib/tailor/rulers/spaces_before_lbrace_ruler.rb +93 -0
  59. data/lib/tailor/rulers/spaces_before_rbrace_ruler.rb +98 -0
  60. data/lib/tailor/rulers/spaces_before_rbracket_ruler.rb +70 -0
  61. data/lib/tailor/rulers/spaces_before_rparen_ruler.rb +70 -0
  62. data/lib/tailor/rulers/spaces_in_empty_braces_ruler.rb +94 -0
  63. data/lib/tailor/rulers/trailing_newlines_ruler.rb +36 -0
  64. data/lib/tailor/runtime_error.rb +3 -0
  65. data/lib/tailor/tailorrc.erb +88 -0
  66. data/lib/tailor/version.rb +2 -2
  67. data/spec/spec_helper.rb +7 -5
  68. data/spec/tailor/cli_spec.rb +94 -0
  69. data/spec/tailor/configuration_spec.rb +147 -0
  70. data/spec/tailor/critic_spec.rb +63 -0
  71. data/spec/tailor/lexed_line_spec.rb +569 -0
  72. data/spec/tailor/lexer/token_spec.rb +46 -0
  73. data/spec/tailor/lexer_spec.rb +181 -0
  74. data/spec/tailor/options_spec.rb +6 -0
  75. data/spec/tailor/problem_spec.rb +74 -0
  76. data/spec/tailor/reporter_spec.rb +53 -0
  77. data/spec/tailor/ruler_spec.rb +56 -0
  78. data/spec/tailor/rulers/indentation_spaces_ruler/indentation_manager_spec.rb +454 -0
  79. data/spec/tailor/rulers/indentation_spaces_ruler_spec.rb +128 -0
  80. data/spec/tailor/rulers/spaces_after_comma_spec.rb +31 -0
  81. data/spec/tailor/rulers/spaces_after_lbrace_ruler_spec.rb +145 -0
  82. data/spec/tailor/rulers/spaces_before_lbrace_ruler_spec.rb +63 -0
  83. data/spec/tailor/rulers/spaces_before_rbrace_ruler_spec.rb +63 -0
  84. data/spec/tailor/rulers_spec.rb +9 -0
  85. data/spec/tailor/version_spec.rb +6 -0
  86. data/spec/tailor_spec.rb +9 -21
  87. data/tailor.gemspec +22 -35
  88. data/tasks/features.rake +7 -0
  89. data/tasks/roodi.rake +9 -0
  90. data/tasks/roodi_config.yaml +14 -0
  91. data/tasks/spec.rake +16 -0
  92. data/tasks/yard.rake +14 -0
  93. metadata +224 -77
  94. data/features/case_checking.feature +0 -38
  95. data/features/spacing.feature +0 -97
  96. data/features/spacing/commas.feature +0 -44
  97. data/features/step_definitions/case_checking_steps.rb +0 -42
  98. data/features/step_definitions/spacing_steps.rb +0 -156
  99. data/features/support/1_file_with_bad_comma_spacing/bad_comma_spacing.rb +0 -43
  100. data/features/support/1_file_with_bad_curly_brace_spacing/bad_curly_brace_spacing.rb +0 -60
  101. data/features/support/1_file_with_bad_parenthesis/bad_parenthesis.rb +0 -4
  102. data/features/support/1_file_with_bad_square_brackets/bad_square_brackets.rb +0 -62
  103. data/features/support/1_file_with_camel_case_class/camel_case_class.rb +0 -5
  104. data/features/support/1_file_with_camel_case_method/camel_case_method.rb +0 -3
  105. data/features/support/1_file_with_hard_tabs/hard_tab.rb +0 -3
  106. data/features/support/1_file_with_long_lines/long_lines.rb +0 -5
  107. data/features/support/1_file_with_snake_case_class/snake_case_class.rb +0 -5
  108. data/features/support/1_file_with_snake_case_method/snake_case_method.rb +0 -3
  109. data/features/support/1_file_with_trailing_whitespace/trailing_whitespace.rb +0 -5
  110. data/features/support/1_good_simple_file/simple_project.rb +0 -5
  111. data/features/support/common.rb +0 -102
  112. data/features/support/matchers.rb +0 -11
  113. data/lib/tailor/file_line.rb +0 -220
  114. data/lib/tailor/indentation.rb +0 -245
  115. data/lib/tailor/spacing.rb +0 -237
  116. data/spec/file_line_spec.rb +0 -70
  117. data/spec/indentation_spec.rb +0 -259
  118. data/spec/spacing/colon_spacing_spec.rb +0 -71
  119. data/spec/spacing/comma_spacing_spec.rb +0 -159
  120. data/spec/spacing/curly_brace_spacing_spec.rb +0 -257
  121. data/spec/spacing/parentheses_spacing_spec.rb +0 -28
  122. data/spec/spacing/square_bracket_spacing_spec.rb +0 -116
  123. data/spec/spacing_spec.rb +0 -167
  124. data/tasks/metrics.rake +0 -23
@@ -0,0 +1,162 @@
1
+ require 'erb'
2
+ require 'yaml'
3
+ require 'fileutils'
4
+ require_relative 'configuration'
5
+ require_relative 'lexer'
6
+ require_relative 'logger'
7
+ require_relative 'ruler'
8
+ require_relative 'rulers'
9
+ require_relative 'runtime_error'
10
+
11
+
12
+ class Tailor
13
+ class Critic
14
+ include Tailor::Logger::Mixin
15
+ include Tailor::Rulers
16
+
17
+ RULER_OBSERVERS = {
18
+ spaces_before_lbrace: [:add_lbrace_observer],
19
+ spaces_after_lbrace: [
20
+ :add_comment_observer,
21
+ :add_ignored_nl_observer,
22
+ :add_lbrace_observer,
23
+ :add_nl_observer
24
+ ],
25
+ spaces_before_rbrace: [
26
+ :add_embexpr_beg_observer,
27
+ :add_lbrace_observer,
28
+ :add_rbrace_observer
29
+ ],
30
+ spaces_after_lbracket: [
31
+ :add_comment_observer,
32
+ :add_ignored_nl_observer,
33
+ :add_lbracket_observer,
34
+ :add_nl_observer
35
+ ],
36
+ spaces_before_rbracket: [:add_rbracket_observer],
37
+ spaces_after_lparen: [
38
+ :add_comment_observer,
39
+ :add_ignored_nl_observer,
40
+ :add_lparen_observer,
41
+ :add_nl_observer
42
+ ],
43
+ spaces_before_rparen: [:add_rparen_observer],
44
+ spaces_in_empty_braces: [
45
+ :add_embexpr_beg_observer,
46
+ :add_lbrace_observer,
47
+ :add_rbrace_observer
48
+ ],
49
+ spaces_before_comma: [
50
+ :add_comma_observer,
51
+ :add_comment_observer,
52
+ :add_ignored_nl_observer,
53
+ :add_nl_observer
54
+ ],
55
+ spaces_after_comma: [
56
+ :add_comma_observer,
57
+ :add_comment_observer,
58
+ :add_ignored_nl_observer,
59
+ :add_nl_observer
60
+ ],
61
+ max_line_length: [:add_ignored_nl_observer, :add_nl_observer],
62
+ indentation_spaces: [
63
+ :add_comment_observer,
64
+ :add_embexpr_beg_observer,
65
+ :add_embexpr_end_observer,
66
+ :add_ignored_nl_observer,
67
+ :add_kw_observer,
68
+ :add_lbrace_observer,
69
+ :add_lbracket_observer,
70
+ :add_lparen_observer,
71
+ :add_nl_observer,
72
+ :add_rbrace_observer,
73
+ :add_rbracket_observer,
74
+ :add_rparen_observer,
75
+ :add_tstring_beg_observer,
76
+ :add_tstring_end_observer
77
+ ],
78
+ allow_trailing_line_spaces: [:add_ignored_nl_observer, :add_nl_observer],
79
+ allow_hard_tabs: [:add_sp_observer],
80
+ allow_camel_case_methods: [:add_ident_observer],
81
+ allow_screaming_snake_case_classes: [:add_const_observer],
82
+ max_code_lines_in_class: [
83
+ :add_ignored_nl_observer,
84
+ :add_kw_observer,
85
+ :add_nl_observer
86
+ ],
87
+ max_code_lines_in_method: [
88
+ :add_ignored_nl_observer,
89
+ :add_kw_observer,
90
+ :add_nl_observer
91
+ ],
92
+ trailing_newlines: [:add_file_observer]
93
+ }
94
+
95
+ def initialize(configuration)
96
+ @file_sets = configuration
97
+ end
98
+
99
+ def critique
100
+ log "file sets keys: #{@file_sets.keys}"
101
+ @file_sets.each do |label, file_set|
102
+ log "Critiquing file_set: #{file_set}"
103
+
104
+ file_set[:file_list].each do |file|
105
+ log "Critiquing file: #{file}"
106
+ problems = check_file(file, file_set[:style])
107
+ yield [problems, label] if block_given?
108
+ end
109
+ end
110
+ end
111
+
112
+ def init_rulers(style, lexer, parent_ruler)
113
+ style.each do |ruler_name, value|
114
+ ruler =
115
+ instance_eval(
116
+ "Tailor::Rulers::#{camelize(ruler_name.to_s)}Ruler.new(#{value})"
117
+ )
118
+ parent_ruler.add_child_ruler(ruler)
119
+ RULER_OBSERVERS[ruler_name].each do |observer|
120
+ log "Adding #{observer} to lexer..."
121
+ lexer.send(observer, ruler)
122
+ end
123
+ end
124
+ end
125
+
126
+ # Converts a snake-case String to a camel-case String.
127
+ #
128
+ # @param [String] string The String to convert.
129
+ # @return [String] The converted String.
130
+ def camelize(string)
131
+ string.split(/_/).map { |word| word.capitalize }.join
132
+ end
133
+
134
+ # Adds problems found from Lexing to the {problems} list.
135
+ #
136
+ # @param [String] file The file to open, read, and check.
137
+ # @return [Hash] The Problems for that file.
138
+ def check_file(file, style)
139
+ log "<#{self.class}> Checking style of file: #{file}."
140
+ lexer = Tailor::Lexer.new(file)
141
+ ruler = Ruler.new
142
+ log "Style: #{style}"
143
+ init_rulers(style, lexer, ruler)
144
+
145
+ lexer.lex
146
+ lexer.check_added_newline
147
+ problems[file] = ruler.problems
148
+
149
+ { file => problems[file] }
150
+ end
151
+
152
+ # @return [Hash]
153
+ def problems
154
+ @problems ||= {}
155
+ end
156
+
157
+ # @return [Fixnum] The number of problems found so far.
158
+ def problem_count
159
+ problems.values.flatten.size
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,126 @@
1
+ require 'text-table'
2
+
3
+ class Tailor
4
+ module Formatter
5
+ class Text
6
+ def line(length=79)
7
+ "##{'-' * length}\n"
8
+ end
9
+
10
+ def file_header(file)
11
+ message = ""
12
+ message << if defined? Term::ANSIColor
13
+ "# #{'File:'.underscore}\n"
14
+ else
15
+ "# File:\n"
16
+ end
17
+
18
+ message << "# #{file}\n"
19
+ message << "#\n"
20
+
21
+ message
22
+ end
23
+
24
+ def file_set_header(file_set)
25
+ message = ""
26
+ message << if defined? Term::ANSIColor
27
+ "# #{'File Set:'.underscore}\n"
28
+ else
29
+ "# File Set:\n"
30
+ end
31
+
32
+ message << "# #{file_set}\n"
33
+ message << "#\n"
34
+
35
+ message
36
+ end
37
+
38
+ def problems_header(problem_list)
39
+ message = ""
40
+ message << if defined? Term::ANSIColor
41
+ "# #{'Problems:'.underscore}\n"
42
+ else
43
+ "# Problems:\n"
44
+ end
45
+
46
+ problem_list.each_with_index do |problem, i|
47
+ position = if problem[:line] == '<EOF>'
48
+ '<EOF>'
49
+ else
50
+ if defined? Term::ANSIColor
51
+ msg = "#{problem[:line].to_s.red.bold}:"
52
+ msg << "#{problem[:column].to_s.red.bold}"
53
+ msg
54
+ else
55
+ "#{problem[:line]}:#{problem[:column]}"
56
+ end
57
+ end
58
+
59
+ message << if defined? Term::ANSIColor
60
+ %Q{# #{(i + 1).to_s.bold}.
61
+ # * position: #{position}
62
+ # * type: #{problem[:type].to_s.red}
63
+ # * message: #{problem[:message].red}
64
+ }
65
+ else
66
+ %Q{# #{(i + 1)}.
67
+ # * position: #{position}
68
+ # * type: #{problem[:type]}
69
+ # * message: #{problem[:message]}
70
+ }
71
+ end
72
+ end
73
+
74
+ message
75
+ end
76
+
77
+ # Prints the report on the file that just got checked.
78
+ #
79
+ # @param [Hash] problems Value should be the file name; keys should be
80
+ # problems with the file.
81
+ def file_report(problems, file_set_label)
82
+ return if problems.values.first.empty?
83
+
84
+ file = problems.keys.first
85
+ problem_list = problems.values.first
86
+ message = line
87
+ message << file_header(file)
88
+ message << file_set_header(file_set_label)
89
+ message << problems_header(problem_list)
90
+
91
+ message << <<-MSG
92
+ #
93
+ #-------------------------------------------------------------------------------
94
+ MSG
95
+
96
+ puts message
97
+ end
98
+
99
+ # Prints the report on all of the files that just got checked.
100
+ #
101
+ # @param [Hash] problems Values are filenames; keys are problems for each
102
+ # of those files.
103
+ def summary_report(problems)
104
+ if problems.empty?
105
+ puts "Your files are in style."
106
+ else
107
+ summary_table = ::Text::Table.new
108
+ summary_table.head = [{ value: "Tailor Summary", colspan: 2 }]
109
+ summary_table.rows << [{ value: "File", align: :center },
110
+ { value: "Total Problems", align: :center }]
111
+ summary_table.rows << :separator
112
+
113
+ problems.each do |file, problem_list|
114
+ summary_table.rows << [file, problem_list.size]
115
+ end
116
+
117
+ summary_table.rows << :separator
118
+ summary_table.rows << ['TOTAL', problems.values.
119
+ map { |v| v.size }.inject(:+)]
120
+
121
+ puts summary_table
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,246 @@
1
+ require_relative 'logger'
2
+ require_relative 'lexer_constants'
3
+ require_relative 'lexer/token'
4
+ require 'ripper'
5
+
6
+ class Tailor
7
+ class LexedLine < Array
8
+ include LexerConstants
9
+
10
+ def initialize(lexed_file, lineno)
11
+ @lineno = lineno
12
+ super(current_line_lex(lexed_file, lineno))
13
+ end
14
+
15
+ # @param [Array] lexed_output The lexed output for the whole file.
16
+ # @return [Array]
17
+ def current_line_lex(lexed_output, lineno)
18
+ lexed_output.find_all { |token| token.first.first == lineno }.uniq
19
+ end
20
+
21
+ # Looks at self and determines if it' s a line of just
22
+ # space characters: spaces, newlines.
23
+ #
24
+ # @return [Boolean]
25
+ def only_spaces?
26
+ element = first_non_space_element
27
+ log "first non-space element '#{element}'"
28
+ element.nil? || element.empty?
29
+ end
30
+
31
+ # @return [Boolean]
32
+ def comment_line?
33
+ first_non_space_element[1] == :on_comment
34
+ end
35
+
36
+ # Checks to see if the current line ends with an operator (not counting the
37
+ # newline that might come after it).
38
+ #
39
+ # @return [Boolean] true if the line ends with an operator; false if not.
40
+ def ends_with_op?
41
+ lexed_line = self.dup
42
+ tokens_in_line = lexed_line.map { |e| e[1] }
43
+
44
+ until tokens_in_line.last != (:on_ignored_nl || :on_nl)
45
+ tokens_in_line.pop
46
+ lexed_line.pop
47
+ end
48
+
49
+ return false if lexed_line.empty?
50
+
51
+ if MULTILINE_OPERATORS.include?(lexed_line.last.last) &&
52
+ tokens_in_line.last == :on_op
53
+ true
54
+ else
55
+ false
56
+ end
57
+ end
58
+
59
+ # Checks to see if the line ends with a keyword, and that the keyword is
60
+ # used as a modifier.
61
+ #
62
+ # @return [Boolean]
63
+ def ends_with_modifier_kw?
64
+ return false unless ends_with_kw?
65
+
66
+ token = Tailor::Lexer::Token.new(last.last,
67
+ { full_line_of_text: to_s })
68
+
69
+ token.modifier_keyword?
70
+ end
71
+
72
+ # @return [Boolean]
73
+ def does_line_end_with(event, exclude_newlines=true)
74
+ if exclude_newlines
75
+ if last_non_line_feed_event.first.empty?
76
+ false
77
+ else
78
+ last_non_line_feed_event[1] == event
79
+ end
80
+ else
81
+ self.last[1] == :on_ignored_nl || self.last[1] == :on_nl
82
+ end
83
+ end
84
+
85
+ # Checks to see if the line contains only +event+ (where it may or may not
86
+ # be preceded by spaces, and is proceeded by a newline).
87
+ #
88
+ # @param [Symbol] event The type of event to check for.
89
+ # @return [Boolean]
90
+ def is_line_only_a(event)
91
+ last_event = last_non_line_feed_event
92
+ return false if last_event[1] != event
93
+
94
+ index = event_index(last_event.first.last)
95
+ previous_event = self.at(index - 1)
96
+
97
+ previous_event.first.last.zero? || previous_event.first.last.nil?
98
+ end
99
+
100
+ def method_missing(meth, *args, &blk)
101
+ if meth.to_s =~ /^ends_with_(.+)\?$/
102
+ event = "on_#{$1}".to_sym
103
+
104
+ if event == :on_ignored_nl || event == :on_nl
105
+ does_line_end_with(event, false)
106
+ else
107
+ does_line_end_with event
108
+ end
109
+ elsif meth.to_s =~ /^only_(.+)\?$/
110
+ event = "on_#{$1}".to_sym
111
+ is_line_only_a(event)
112
+ else
113
+ super(meth, *args, &blk)
114
+ end
115
+ end
116
+
117
+ # Gets the first non-space element from a line of lexed output.
118
+ #
119
+ # @return [Array] The element; +nil+ if none is found.
120
+ def first_non_space_element
121
+ self.find do |e|
122
+ e[1] != :on_sp && e[1] != :on_nl && e[1] != :on_ignored_nl
123
+ end
124
+ end
125
+
126
+ # Checks to see if the current line is a keyword loop (for, while, until)
127
+ # that uses the optional 'do' at the end of the statement.
128
+ #
129
+ # @return [Boolean]
130
+ def loop_with_do?
131
+ keyword_elements = self.find_all { |e| e[1] == :on_kw }
132
+ keyword_tokens = keyword_elements.map { |e| e.last }
133
+ loop_start = keyword_tokens.any? { |t| LOOP_KEYWORDS.include? t }
134
+ with_do = keyword_tokens.any? { |t| t == 'do' }
135
+
136
+ loop_start && with_do
137
+ end
138
+
139
+ # @return [Boolean] +true+ if the line contains an keyword and it is in
140
+ # +KEYWORDS_TO_INDENT.
141
+ def contains_keyword_to_indent?
142
+ self.any? do |e|
143
+ e[1] == :on_kw && KEYWORDS_TO_INDENT.include?(e[2])
144
+ end
145
+ end
146
+
147
+ # @return [Array] The lexed event that represents the last event in the
148
+ # line that's not a +\n+.
149
+ def last_non_line_feed_event
150
+ self.find_all { |e| e[1] != :on_nl && e[1] != :on_ignored_nl }.last ||
151
+ [[]]
152
+ end
153
+
154
+ # @return [Fixnum] The length of the line minus the +\n+.
155
+ def line_length
156
+ event = last_non_line_feed_event
157
+ return 0 if event.first.empty?
158
+
159
+ event.first.last + event.last.size
160
+ end
161
+
162
+ # @param [Fixnum] column Number of the column to get the event for.
163
+ # @return [Array] The event at the given column.
164
+ def event_at column
165
+ self.find { |e| e.first.last == column }
166
+ end
167
+
168
+ # Useful for inspecting events relevant to this one.
169
+ #
170
+ # @example
171
+ # i = lexed_line.event_index(11)
172
+ # previous_event = lexed_line.at(i - 1)
173
+ # @param [Fixnum] column Number of the column of which event to get the
174
+ # index for.
175
+ # @return [Fixnum] The index within +self+ that the event is at.
176
+ def event_index column
177
+ column_event = self.event_at column
178
+ self.index(column_event)
179
+ end
180
+
181
+ # @return [String] The string reassembled from self's tokens.
182
+ def to_s
183
+ self.inject('') { |new_string, e| new_string << e.last }
184
+ end
185
+
186
+ # If a trailing comment exists in the line, remove it and the spaces that
187
+ # come before it. This is necessary, as {Ripper} doesn't trigger an event
188
+ # for the end of the line when the line ends with a comment. Without this
189
+ # observers that key off ending the line will never get triggered, and thus
190
+ # style won't get checked for that line.
191
+ #
192
+ # @param [Fixnum] column The column that the comment is at.
193
+ # @param [String] file_text The whole file's worth of text. Required in
194
+ # order to be able to reconstruct the context in which the line exists.
195
+ # @return [LexedLine] The current lexed line, but with the trailing comment
196
+ # removed.
197
+ def remove_trailing_comment(file_text)
198
+ file_lines = file_text.split("\n")
199
+ lineno = self.last.first.first
200
+ column = self.last.first.last
201
+ log "Removing comment event at #{lineno}:#{column}."
202
+
203
+ comment_index = event_index(column)
204
+ self.delete_at(comment_index)
205
+ self.insert(comment_index, [[lineno, column], :on_nl, "\n"])
206
+ log "Inserted newline for comma; self is now #{self.inspect}"
207
+
208
+ if self.at(comment_index - 1)[1] == :on_sp
209
+ self.delete_at(comment_index - 1)
210
+ end
211
+
212
+ new_text = self.to_s
213
+ log "New line as text: '#{new_text}'"
214
+
215
+ file_lines.delete_at(lineno - 1)
216
+ file_lines.insert(lineno - 1, new_text)
217
+ file_lines = file_lines.join("\n")
218
+
219
+ ripped_output = ::Ripper.lex(file_lines)
220
+ LexedLine.new(ripped_output, lineno)
221
+ end
222
+
223
+ # Determines if the current lexed line is just the end of a tstring.
224
+ #
225
+ # @param [Array] lexed_line_output The lexed output for the current line.
226
+ # @return [Boolean] +true+ if the line contains a +:on_tstring_end+ and
227
+ # not a +:on_tstring_beg+.
228
+ def end_of_multi_line_string?
229
+ self.any? { |e| e[1] == :on_tstring_end } &&
230
+ self.none? { |e| e[1] == :on_tstring_beg }
231
+ end
232
+
233
+ #---------------------------------------------------------------------------
234
+ # Privates!
235
+ #---------------------------------------------------------------------------
236
+ private
237
+
238
+ def log(*args)
239
+ l = begin; lineno; rescue; "<EOF>"; end
240
+ c = begin; column; rescue; "<EOF>"; end
241
+ subclass_name = self.class.to_s.sub(/^Tailor::/, '')
242
+ args.first.insert(0, "<#{subclass_name}> #{l}[#{c}]: ")
243
+ Tailor::Logger.log(*args)
244
+ end
245
+ end
246
+ end