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.
- data/.gitignore +9 -1
- data/.rspec +2 -1
- data/.tailor +6 -0
- data/Gemfile.lock +47 -78
- data/{ChangeLog.rdoc → History.rdoc} +0 -0
- data/README.rdoc +157 -24
- data/Rakefile +0 -9
- data/bin/tailor +16 -69
- data/features/configurable.feature +78 -0
- data/features/horizontal_spacing.feature +262 -0
- data/features/indentation.feature +17 -21
- data/features/indentation/bad_files_with_no_trailing_newline.feature +90 -0
- data/features/indentation/good_files_with_no_trailing_newline.feature +206 -0
- data/features/name_detection.feature +72 -0
- data/features/step_definitions/indentation_steps.rb +10 -133
- data/features/support/env.rb +7 -15
- data/features/support/file_cases/horizontal_spacing_cases.rb +265 -0
- data/features/support/file_cases/indentation_cases.rb +972 -0
- data/features/support/file_cases/naming_cases.rb +52 -0
- data/features/support/file_cases/vertical_spacing_cases.rb +70 -0
- data/features/support/hooks.rb +8 -0
- data/features/support/{1_file_with_bad_operator_spacing → legacy}/bad_op_spacing.rb +0 -0
- data/features/support/{1_file_with_bad_ternary_colon_spacing → legacy}/bad_ternary_colon_spacing.rb +0 -0
- data/features/support/{1_long_file_with_indentation/my_project.rb → legacy/long_file_with_indentation.rb} +1 -1
- data/features/support/world.rb +14 -0
- data/features/vertical_spacing.feature +114 -0
- data/lib/ext/string_ext.rb +5 -0
- data/lib/tailor.rb +6 -252
- data/lib/tailor/cli.rb +49 -0
- data/lib/tailor/cli/options.rb +251 -0
- data/lib/tailor/composite_observable.rb +56 -0
- data/lib/tailor/configuration.rb +263 -0
- data/lib/tailor/critic.rb +162 -0
- data/lib/tailor/formatters/text.rb +126 -0
- data/lib/tailor/lexed_line.rb +246 -0
- data/lib/tailor/lexer.rb +428 -0
- data/lib/tailor/lexer/token.rb +103 -0
- data/lib/tailor/lexer_constants.rb +75 -0
- data/lib/tailor/logger.rb +28 -0
- data/lib/tailor/problem.rb +100 -0
- data/lib/tailor/reporter.rb +48 -0
- data/lib/tailor/ruler.rb +39 -0
- data/lib/tailor/rulers.rb +7 -0
- data/lib/tailor/rulers/allow_camel_case_methods_ruler.rb +30 -0
- data/lib/tailor/rulers/allow_hard_tabs_ruler.rb +22 -0
- data/lib/tailor/rulers/allow_screaming_snake_case_classes_ruler.rb +32 -0
- data/lib/tailor/rulers/allow_trailing_line_spaces_ruler.rb +33 -0
- data/lib/tailor/rulers/indentation_spaces_ruler.rb +199 -0
- data/lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb +362 -0
- data/lib/tailor/rulers/max_code_lines_in_class_ruler.rb +84 -0
- data/lib/tailor/rulers/max_code_lines_in_method_ruler.rb +84 -0
- data/lib/tailor/rulers/max_line_length_ruler.rb +31 -0
- data/lib/tailor/rulers/spaces_after_comma_ruler.rb +83 -0
- data/lib/tailor/rulers/spaces_after_lbrace_ruler.rb +114 -0
- data/lib/tailor/rulers/spaces_after_lbracket_ruler.rb +123 -0
- data/lib/tailor/rulers/spaces_after_lparen_ruler.rb +116 -0
- data/lib/tailor/rulers/spaces_before_comma_ruler.rb +67 -0
- data/lib/tailor/rulers/spaces_before_lbrace_ruler.rb +93 -0
- data/lib/tailor/rulers/spaces_before_rbrace_ruler.rb +98 -0
- data/lib/tailor/rulers/spaces_before_rbracket_ruler.rb +70 -0
- data/lib/tailor/rulers/spaces_before_rparen_ruler.rb +70 -0
- data/lib/tailor/rulers/spaces_in_empty_braces_ruler.rb +94 -0
- data/lib/tailor/rulers/trailing_newlines_ruler.rb +36 -0
- data/lib/tailor/runtime_error.rb +3 -0
- data/lib/tailor/tailorrc.erb +88 -0
- data/lib/tailor/version.rb +2 -2
- data/spec/spec_helper.rb +7 -5
- data/spec/tailor/cli_spec.rb +94 -0
- data/spec/tailor/configuration_spec.rb +147 -0
- data/spec/tailor/critic_spec.rb +63 -0
- data/spec/tailor/lexed_line_spec.rb +569 -0
- data/spec/tailor/lexer/token_spec.rb +46 -0
- data/spec/tailor/lexer_spec.rb +181 -0
- data/spec/tailor/options_spec.rb +6 -0
- data/spec/tailor/problem_spec.rb +74 -0
- data/spec/tailor/reporter_spec.rb +53 -0
- data/spec/tailor/ruler_spec.rb +56 -0
- data/spec/tailor/rulers/indentation_spaces_ruler/indentation_manager_spec.rb +454 -0
- data/spec/tailor/rulers/indentation_spaces_ruler_spec.rb +128 -0
- data/spec/tailor/rulers/spaces_after_comma_spec.rb +31 -0
- data/spec/tailor/rulers/spaces_after_lbrace_ruler_spec.rb +145 -0
- data/spec/tailor/rulers/spaces_before_lbrace_ruler_spec.rb +63 -0
- data/spec/tailor/rulers/spaces_before_rbrace_ruler_spec.rb +63 -0
- data/spec/tailor/rulers_spec.rb +9 -0
- data/spec/tailor/version_spec.rb +6 -0
- data/spec/tailor_spec.rb +9 -21
- data/tailor.gemspec +22 -35
- data/tasks/features.rake +7 -0
- data/tasks/roodi.rake +9 -0
- data/tasks/roodi_config.yaml +14 -0
- data/tasks/spec.rake +16 -0
- data/tasks/yard.rake +14 -0
- metadata +224 -77
- data/features/case_checking.feature +0 -38
- data/features/spacing.feature +0 -97
- data/features/spacing/commas.feature +0 -44
- data/features/step_definitions/case_checking_steps.rb +0 -42
- data/features/step_definitions/spacing_steps.rb +0 -156
- data/features/support/1_file_with_bad_comma_spacing/bad_comma_spacing.rb +0 -43
- data/features/support/1_file_with_bad_curly_brace_spacing/bad_curly_brace_spacing.rb +0 -60
- data/features/support/1_file_with_bad_parenthesis/bad_parenthesis.rb +0 -4
- data/features/support/1_file_with_bad_square_brackets/bad_square_brackets.rb +0 -62
- data/features/support/1_file_with_camel_case_class/camel_case_class.rb +0 -5
- data/features/support/1_file_with_camel_case_method/camel_case_method.rb +0 -3
- data/features/support/1_file_with_hard_tabs/hard_tab.rb +0 -3
- data/features/support/1_file_with_long_lines/long_lines.rb +0 -5
- data/features/support/1_file_with_snake_case_class/snake_case_class.rb +0 -5
- data/features/support/1_file_with_snake_case_method/snake_case_method.rb +0 -3
- data/features/support/1_file_with_trailing_whitespace/trailing_whitespace.rb +0 -5
- data/features/support/1_good_simple_file/simple_project.rb +0 -5
- data/features/support/common.rb +0 -102
- data/features/support/matchers.rb +0 -11
- data/lib/tailor/file_line.rb +0 -220
- data/lib/tailor/indentation.rb +0 -245
- data/lib/tailor/spacing.rb +0 -237
- data/spec/file_line_spec.rb +0 -70
- data/spec/indentation_spec.rb +0 -259
- data/spec/spacing/colon_spacing_spec.rb +0 -71
- data/spec/spacing/comma_spacing_spec.rb +0 -159
- data/spec/spacing/curly_brace_spacing_spec.rb +0 -257
- data/spec/spacing/parentheses_spacing_spec.rb +0 -28
- data/spec/spacing/square_bracket_spacing_spec.rb +0 -116
- data/spec/spacing_spec.rb +0 -167
- 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
|