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,75 @@
1
+ class Tailor
2
+
3
+ # These are important tokens that key certain styling events. They are taken
4
+ # from:
5
+ # https://github.com/ruby/ruby/blob/trunk/ext/ripper/eventids2.c
6
+ # https://github.com/ruby/ruby/blob/trunk/parse.y
7
+ module LexerConstants
8
+ KEYWORDS_TO_INDENT = [
9
+ 'begin',
10
+ 'case',
11
+ 'class',
12
+ 'def',
13
+ 'do',
14
+ 'else',
15
+ 'elsif',
16
+ 'ensure',
17
+ 'for',
18
+ 'if',
19
+ 'module',
20
+ 'rescue',
21
+ 'unless',
22
+ 'until',
23
+ 'when',
24
+ 'while'
25
+ ]
26
+
27
+ CONTINUATION_KEYWORDS = [
28
+ 'elsif',
29
+ 'else',
30
+ 'ensure',
31
+ 'rescue',
32
+ 'when'
33
+ ]
34
+
35
+ KEYWORDS_AND_MODIFIERS = [
36
+ 'if',
37
+ 'unless',
38
+ 'until',
39
+ 'while'
40
+ ]
41
+
42
+ MODIFIERS = {
43
+ 'if' => :if_mod,
44
+ 'rescue' => :rescue_mod,
45
+ 'unless' => :unless_mod,
46
+ 'until' => :until_mod,
47
+ 'while' => :while_mod
48
+ }
49
+
50
+ MULTILINE_OPERATORS = [
51
+ '+', '-', '*', '**', '/', '%', # +, -, tSTAR, tPOW, /, %
52
+ '<', '>', '<=', '>=', # <, >, tLEQ, tGEQ
53
+ '=', '+=', '-=', '*=', '**=', '/=', '%=',
54
+ '&&=', '||=', '<<=', # ...tOP_ASGN...
55
+ '>>', '<<', # tRSHFT, tLSHFT
56
+ '!', '&', '?', ':', '^', '~', # !, tAMPER, ?, :, ^, ~
57
+ #'|',
58
+ '&&', '||', # tANDOP, tOROP
59
+ '==', '===', '<=>', '!=', # tEQ, tEQQ, tCMP, tNEQ
60
+ '=~', '!~', # tMATCH, tNMATCH
61
+ '..', '...', # tDOT2, tDOT3
62
+ '::', # tCOLON2 (not sure about tCOLON3)
63
+ #'[]', '[]=', # tAREF, tASET (not sure about these)
64
+ '=>', # tASSOC
65
+ '->', # tLAMBDA
66
+ '~>' # gem_version op
67
+ ]
68
+
69
+ LOOP_KEYWORDS = [
70
+ 'for',
71
+ 'until',
72
+ 'while'
73
+ ]
74
+ end
75
+ end
@@ -0,0 +1,28 @@
1
+ require 'log_switch'
2
+
3
+ class Tailor
4
+ class Logger
5
+ extend LogSwitch
6
+
7
+ def self.logger
8
+ return @logger if @logger
9
+ @logger ||= ::Logger.new $stdout
10
+
11
+ def @logger.format_message(level, time, progname, msg)
12
+ "[#{time.strftime("%Y-%m-%d %H:%M:%S")}] #{msg}\n"
13
+ end
14
+
15
+ @logger
16
+ end
17
+
18
+ module Mixin
19
+ def self.included(base)
20
+ define_method :log do |*args|
21
+ class_minus_main_name = self.class.to_s.sub(/^.*::/, '')
22
+ args.first.insert(0, "<#{class_minus_main_name}> ")
23
+ Tailor::Logger.log(*args)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,100 @@
1
+ require_relative 'logger'
2
+ require_relative 'runtime_error'
3
+
4
+ class Tailor
5
+
6
+ # A Hashed data structure that abstracts out data (especially the error
7
+ # message) to build reports from.
8
+ class Problem < Hash
9
+ include LogSwitch::Mixin
10
+
11
+ # @param [Symbol] type The problem type.
12
+ # @param [Binding] binding The context that the problem was discovered in.
13
+ def initialize(type, line, column, options={})
14
+ @type = type
15
+ @line = line
16
+ @column = column
17
+ @options = options
18
+ set_values
19
+ subclass_name = self.class.to_s.sub(/^Tailor::/, '')
20
+ msg = "<#{subclass_name}> #{self[:line]}[#{self[:column]}]: "
21
+ msg << "ERROR[:#{self[:type]}] #{self[:message]}"
22
+ log msg
23
+ end
24
+
25
+ # Sets the standard values for the problem based on the type and binding.
26
+ def set_values
27
+ self[:type] = @type
28
+ self[:line] = @line
29
+ self[:column] = @column
30
+ self[:message] = message(@type)
31
+ end
32
+
33
+ # Builds the message for the problem type, based on the info provided in
34
+ # the +@binding+.
35
+ #
36
+ # @param [Symbol] type The type of problem.
37
+ # @return [String] The error message.
38
+ def message(type)
39
+ case type
40
+ when :camel_case_method
41
+ "Camel-case method name found."
42
+ when :code_lines_in_class
43
+ msg = "Class/module has #{@options[:actual_count]} code lines, but "
44
+ msg << "should have no more than #{@options[:should_be_at]}."
45
+ when :code_lines_in_method
46
+ msg = "Method has #{@options[:actual_count]} code lines, but "
47
+ msg << "should have no more than #{@options[:should_be_at]}."
48
+ when :hard_tab
49
+ "Hard tab found."
50
+ when :indentation
51
+ self[:column] = @options[:actual_indentation]
52
+ msg = "Line is indented to #{@options[:actual_indentation]}, "
53
+ msg << "but should be at #{@options[:should_be_at]}."
54
+ when :line_length
55
+ msg = "Line is #{@options[:actual_length]} chars long, "
56
+ msg << "but should be #{@options[:should_be_at]}."
57
+ when :screaming_snake_case_class_name
58
+ "Screaming-snake-case class/module found."
59
+ when :spaces_after_comma
60
+ msg = "Line has #{@options[:actual_spaces]} space(s) after a comma, "
61
+ msg << "but should have #{@options[:should_have]}."
62
+ when :spaces_before_comma
63
+ msg = "Line has #{@options[:actual_spaces]} space(s) before a comma, "
64
+ msg << "but should have #{@options[:should_have]}."
65
+ when :spaces_after_lbrace
66
+ msg = "Line has #{@options[:actual_spaces]} space(s) after a {, "
67
+ msg << "but should have #{@options[:should_have]}."
68
+ when :spaces_after_lbracket
69
+ msg = "Line has #{@options[:actual_spaces]} space(s) after a [, "
70
+ msg << "but should have #{@options[:should_have]}."
71
+ when :spaces_after_lparen
72
+ msg = "Line has #{@options[:actual_spaces]} space(s) after a (, "
73
+ msg << "but should have #{@options[:should_have]}."
74
+ when :spaces_before_lbrace
75
+ msg = "Line has #{@options[:actual_spaces]} space(s) before a {, "
76
+ msg << "but should have #{@options[:should_have]}."
77
+ when :spaces_before_rbrace
78
+ msg = "Line has #{@options[:actual_spaces]} space(s) before a }, "
79
+ msg << "but should have #{@options[:should_have]}."
80
+ when :spaces_before_rbracket
81
+ msg = "Line has #{@options[:actual_spaces]} space(s) before a ], "
82
+ msg << "but should have #{@options[:should_have]}."
83
+ when :spaces_before_rparen
84
+ msg = "Line has #{@options[:actual_spaces]} space(s) before a ), "
85
+ msg << "but should have #{@options[:should_have]}."
86
+ when :spaces_in_empty_braces
87
+ msg = "Line has #{@options[:actual_spaces]} space(s) in between empty "
88
+ msg << "braces, but should have #{@options[:should_have]}."
89
+ when :trailing_newlines
90
+ msg = "File has #{@options[:actual_trailing_newlines]} trailing "
91
+ msg << "newlines, but should have #{@options[:should_have]}."
92
+ when :trailing_spaces
93
+ "Line has #{@options[:actual_trailing_spaces]} trailing spaces."
94
+ else
95
+ raise Tailor::RuntimeError,
96
+ "Problem type '#{type}' doesn't exist."
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,48 @@
1
+ class Tailor
2
+
3
+ # Objects of this type are responsible for sending the right data to report
4
+ # formatters.
5
+ class Reporter
6
+ attr_reader :formatters
7
+
8
+ # For each in +formats+, it creates a new
9
+ # +Tailor::Formatter::#{formatter.capitalize}+ object and adds it to
10
+ # +@formatters+.
11
+ #
12
+ # @param [Array] formats A list of formatters to use for generating reports.
13
+ def initialize(*formats)
14
+ @formatters = []
15
+
16
+ formats.flatten.each do |formatter|
17
+ require_relative "formatters/#{formatter}"
18
+ @formatters << eval("Tailor::Formatter::#{formatter.capitalize}.new")
19
+ end
20
+ end
21
+
22
+ # Sends the data to each +@formatters+ to generate the report of problems
23
+ # for the file that was just critiqued. A problem is in the format:
24
+ #
25
+ # { 'path/to/file.rb' => [Problem1, Problem2, etc.]}
26
+ #
27
+ # ...where Problem1 and Problem2 are of type {Tailor::Problem}.
28
+ #
29
+ # @param [Hash] file_problems
30
+ # @param [Symbol,String] label The label of the file_set that defines the
31
+ # problems in +file_problems+.
32
+ def file_report(file_problems, label)
33
+ @formatters.each do |formatter|
34
+ formatter.file_report(file_problems, label)
35
+ end
36
+ end
37
+
38
+ # Sends the data to each +@formatters+ to generate the reports of problems
39
+ # for all files that were just critiqued.
40
+ #
41
+ # @param [Hash] all_problems
42
+ def summary_report(all_problems)
43
+ @formatters.each do |formatter|
44
+ formatter.summary_report(all_problems)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,39 @@
1
+ require_relative 'logger'
2
+ require_relative 'problem'
3
+ require_relative 'runtime_error'
4
+
5
+ class Tailor
6
+ class Ruler
7
+ include Tailor::Logger::Mixin
8
+
9
+ attr_reader :cli_option
10
+
11
+ def initialize(config={})
12
+ @config = config
13
+ @problems = []
14
+ @child_rulers = []
15
+ @cli_option = ""
16
+ @do_measurement = true
17
+ log "Ruler initialized with style setting: #{@config}"
18
+ end
19
+
20
+ def add_child_ruler(ruler)
21
+ @child_rulers << ruler
22
+ log "Added child: #{ruler}"
23
+ end
24
+
25
+ def problems
26
+ @problems = @child_rulers.inject(@problems) do |problems, ruler|
27
+ problems + ruler.problems
28
+ end
29
+
30
+ @problems.sort_by! { |problem| problem[:line].to_i }
31
+ end
32
+
33
+ # Each ruler should redefine this for its needs.
34
+ def measure(*args)
35
+ raise RuntimeError,
36
+ "Ruler#measure called, but should be redefined by a real ruler."
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ Dir.glob(File.dirname(__FILE__) + '/rulers/*', &method(:require))
2
+
3
+ class Tailor
4
+ module Rulers
5
+
6
+ end
7
+ end
@@ -0,0 +1,30 @@
1
+ require_relative '../ruler'
2
+
3
+ class Tailor
4
+ module Rulers
5
+ class AllowCamelCaseMethodsRuler < Tailor::Ruler
6
+ def ident_update(token, lexed_line, lineno, column)
7
+ ident_index = lexed_line.event_index(column)
8
+ previous_event = lexed_line.event_at(ident_index - 2)
9
+ log "previous event: #{previous_event}"
10
+
11
+ return if previous_event.nil?
12
+
13
+ if previous_event[1] == :on_kw && previous_event.last == "def"
14
+ measure(token, lineno, column)
15
+ end
16
+ end
17
+
18
+ # Checks to see if the method name contains capital letters.
19
+ #
20
+ # @param [Fixnum] token The method name.
21
+ # @param [Fixnum] lineno Line the problem was found on.
22
+ # @param [Fixnum] column Column the problem was found on.
23
+ def measure(token, lineno, column)
24
+ if token.contains_capital_letter?
25
+ @problems << Problem.new(:camel_case_method, lineno, column)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ require_relative '../ruler'
2
+
3
+ class Tailor
4
+ module Rulers
5
+ class AllowHardTabsRuler < Tailor::Ruler
6
+ def sp_update(token, lineno, column)
7
+ measure(token, lineno, column)
8
+ end
9
+
10
+ # Checks to see if the space(s) contains hard tabs.
11
+ #
12
+ # @param [Fixnum] token The space(s).
13
+ # @param [Fixnum] lineno Line the problem was found on.
14
+ # @param [Fixnum] column Column the problem was found on.
15
+ def measure(token, lineno, column)
16
+ if token.contains_hard_tab?
17
+ @problems << Problem.new(:hard_tab, lineno, column)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,32 @@
1
+ require_relative '../ruler'
2
+
3
+ class Tailor
4
+ module Rulers
5
+ class AllowScreamingSnakeCaseClassesRuler < Tailor::Ruler
6
+ def const_update(token, lexed_line, lineno, column)
7
+ ident_index = lexed_line.event_index(column)
8
+ previous_event = lexed_line.event_at(ident_index - 2)
9
+ log "previous event: #{previous_event}"
10
+
11
+ return if previous_event.nil?
12
+
13
+ if previous_event[1] == :on_kw &&
14
+ (previous_event.last == "class" || previous_event.last == "module")
15
+ measure(token, lineno, column)
16
+ end
17
+ end
18
+
19
+ # Checks to see if the class name matches /[A-Z].*_/.
20
+ #
21
+ # @param [Fixnum] token The space(s).
22
+ # @param [Fixnum] lineno Line the potential problem is on.
23
+ # @param [Fixnum] column Column the potential problem is on.
24
+ def measure(token, lineno, column)
25
+ if token.screaming_snake_case?
26
+ @problems << Problem.new(:screaming_snake_case_class_name,
27
+ lineno, column)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ require_relative '../ruler'
2
+
3
+ class Tailor
4
+ module Rulers
5
+ class AllowTrailingLineSpacesRuler < Tailor::Ruler
6
+ def ignored_nl_update(lexed_line, lineno, column)
7
+ log "Last event: #{lexed_line.last_non_line_feed_event}"
8
+ log "Line ends with space: #{lexed_line.ends_with_sp?}"
9
+
10
+ measure(lexed_line, lineno, column)
11
+ end
12
+
13
+ def nl_update(lexed_line, lineno, column)
14
+ ignored_nl_update(lexed_line, lineno, column)
15
+ end
16
+
17
+ # Checks to see if the line contains trailing spaces.
18
+ #
19
+ # @param [LexedLine] lexed_line The line to check for trailing spaces.
20
+ # @param [Fixnum] lineno Line the potential problem is on.
21
+ # @param [Fixnum] column Column the potential problem is on.
22
+ def measure(lexed_line, lineno, column)
23
+ if lexed_line.ends_with_sp?
24
+ options = {
25
+ actual_trailing_spaces:
26
+ lexed_line.last_non_line_feed_event.last.size
27
+ }
28
+ @problems << Problem.new(:trailing_spaces, lineno, column, options)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,199 @@
1
+ require_relative '../ruler'
2
+ require_relative '../lexed_line'
3
+ require_relative '../lexer/token'
4
+ require_relative 'indentation_spaces_ruler/indentation_manager'
5
+
6
+ class Tailor
7
+ module Rulers
8
+ class IndentationSpacesRuler < Tailor::Ruler
9
+ def initialize(config)
10
+ super(config)
11
+ @manager = IndentationManager.new(@config)
12
+ end
13
+
14
+ def comment_update(token, lexed_line, file_text, lineno, column)
15
+ # trailing comment?
16
+ if token.ends_with_newline?
17
+ log "Comment ends with newline. Removing comment..."
18
+ log "Old lexed line: #{lexed_line.inspect}"
19
+
20
+ new_lexed_line = lexed_line.remove_trailing_comment(file_text)
21
+
22
+ log "New lexed line: #{new_lexed_line.inspect}"
23
+
24
+ if new_lexed_line.ends_with_ignored_nl?
25
+ log "New lexed line ends with :on_ignored_nl."
26
+ ignored_nl_update(new_lexed_line, lineno, column)
27
+ elsif new_lexed_line.ends_with_nl?
28
+ log "New lexed line ends with :on_nl."
29
+ nl_update(new_lexed_line, lineno, column)
30
+ end
31
+ end
32
+ end
33
+
34
+ def embexpr_beg_update
35
+ @manager.embexpr_beg = true
36
+ end
37
+
38
+ def embexpr_end_update
39
+ @manager.embexpr_beg = false
40
+ end
41
+
42
+ def ignored_nl_update(current_lexed_line, lineno, column)
43
+ log "indent reasons on entry: #{@manager.indent_reasons}"
44
+ stop if @manager.tstring_nesting.size > 0
45
+
46
+ if current_lexed_line.only_spaces?
47
+ log "Line of only spaces. Moving on."
48
+ # todo: maybe i shouldn't return here? ...do transitions?
49
+ return
50
+ end
51
+
52
+ if @manager.line_ends_with_single_token_indenter?(current_lexed_line)
53
+ log "Line ends with single-token indent token."
54
+
55
+ unless @manager.comma_is_part_of_enclosed_statement?(current_lexed_line, lineno)
56
+ log "Line-ending single-token indenter found."
57
+ token_event = current_lexed_line.last_non_line_feed_event
58
+
59
+ 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)
62
+ end
63
+ end
64
+ end
65
+
66
+ @manager.update_actual_indentation(current_lexed_line)
67
+ @manager.set_up_line_transition
68
+ measure(lineno, column)
69
+
70
+ log "indent reasons on exit: #{@manager.indent_reasons}"
71
+ # prep for next line
72
+ @manager.transition_lines
73
+ end
74
+
75
+ def kw_update(token, lexed_line, lineno, column)
76
+ if token == "end"
77
+ @manager.update_for_closing_reason(:on_kw, lexed_line, lineno)
78
+ return
79
+ end
80
+
81
+ if token.continuation_keyword?
82
+ log "Continuation keyword found: '#{token}'."
83
+ @manager.update_for_continuation_reason(token, lexed_line, lineno)
84
+ return
85
+ end
86
+
87
+ if token.keyword_to_indent?
88
+ log "Indent keyword found: '#{token}'."
89
+ @manager.update_for_opening_reason(:on_kw, token, lineno)
90
+ end
91
+ end
92
+
93
+ def lbrace_update(lexed_line, lineno, column)
94
+ token = Tailor::Lexer::Token.new('{')
95
+ @manager.update_for_opening_reason(:on_lbrace, token, lineno)
96
+ end
97
+
98
+ def lbracket_update(lexed_line, lineno, column)
99
+ token = Tailor::Lexer::Token.new('[')
100
+ @manager.update_for_opening_reason(:on_lbracket, token, lineno)
101
+ end
102
+
103
+ def lparen_update(lineno, column)
104
+ token = Tailor::Lexer::Token.new('(')
105
+ @manager.update_for_opening_reason(:on_lparen, token, lineno)
106
+ end
107
+
108
+ def nl_update(current_lexed_line, lineno, column)
109
+ log "indent reasons on entry: #{@manager.indent_reasons}"
110
+ @manager.update_actual_indentation(current_lexed_line)
111
+
112
+ if @manager.last_indent_reason_type != :on_kw &&
113
+ @manager.last_indent_reason_type != :on_lbrace &&
114
+ @manager.last_indent_reason_type != :on_lbracket &&
115
+ @manager.last_indent_reason_type != :on_lparen &&
116
+ !@manager.last_indent_reason_type.nil?
117
+ log "last indent reason type: #{@manager.last_indent_reason_type}"
118
+ log "I think this is a single-token closing line..."
119
+
120
+ @manager.update_for_closing_reason(@manager.indent_reasons.last[:event_type],
121
+ current_lexed_line, lineno)
122
+ end
123
+
124
+ @manager.set_up_line_transition
125
+
126
+ unless current_lexed_line.end_of_multi_line_string?
127
+ measure(lineno, column)
128
+ end
129
+
130
+ log "indent reasons on exit: #{@manager.indent_reasons}"
131
+ @manager.transition_lines
132
+ end
133
+
134
+ def rbrace_update(current_lexed_line, lineno, column)
135
+ if @manager.multi_line_braces?(lineno)
136
+ log "End of multi-line braces!"
137
+
138
+ if current_lexed_line.only_rbrace?
139
+ @manager.amount_to_change_this -= 1
140
+ log "lonely rbrace. change_this -= 1 -> #{@manager.amount_to_change_this}"
141
+ end
142
+ end
143
+
144
+ @manager.update_for_closing_reason(:on_rbrace, current_lexed_line, lineno)
145
+ end
146
+
147
+ def rbracket_update(current_lexed_line, lineno, column)
148
+ if @manager.multi_line_brackets?(lineno)
149
+ log "End of multi-line brackets!"
150
+
151
+ if current_lexed_line.only_rbracket?
152
+ @manager.amount_to_change_this -= 1
153
+ log "lonely rbracket. change_this -= 1 -> #{@manager.amount_to_change_this}"
154
+ end
155
+ end
156
+
157
+ @manager.update_for_closing_reason(:on_rbracket, current_lexed_line, lineno)
158
+ end
159
+
160
+ def rparen_update(current_lexed_line, lineno, column)
161
+ if @manager.multi_line_parens?(lineno)
162
+ log "End of multi-line parens!"
163
+
164
+ if current_lexed_line.only_rparen?
165
+ @manager.amount_to_change_this -= 1
166
+ log "lonely rparen. change_this -= 1 -> #{@manager.amount_to_change_this}"
167
+ end
168
+ end
169
+
170
+ @manager.update_for_closing_reason(:on_rparen, current_lexed_line, lineno)
171
+ end
172
+
173
+ def tstring_beg_update(lineno)
174
+ @manager.tstring_nesting << lineno
175
+ @manager.stop
176
+ end
177
+
178
+ def tstring_end_update
179
+ @manager.tstring_nesting.pop
180
+ @manager.start unless @manager.in_tstring?
181
+ end
182
+
183
+ # Checks if the line's indentation level is appropriate.
184
+ #
185
+ # @param [Fixnum] lineno The line the potential problem is on.
186
+ # @param [Fixnum] column The column the potential problem is on.
187
+ def measure(lineno, column)
188
+ log "Measuring..."
189
+
190
+ 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
+ )
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end