tailor 0.1.5 → 1.0.0.alpha

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 (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