tailor 1.0.0.alpha2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. data/.gitignore +1 -0
  2. data/.tailor +10 -2
  3. data/Gemfile.lock +2 -2
  4. data/History.rdoc +20 -0
  5. data/README.rdoc +176 -26
  6. data/features/configurable.feature +19 -39
  7. data/features/horizontal_spacing.feature +3 -2
  8. data/features/indentation.feature +2 -2
  9. data/features/indentation/bad_files_with_no_trailing_newline.feature +9 -8
  10. data/features/indentation/good_files_with_no_trailing_newline.feature +19 -6
  11. data/features/name_detection.feature +2 -2
  12. data/features/support/env.rb +0 -2
  13. data/features/support/file_cases/horizontal_spacing_cases.rb +5 -4
  14. data/features/support/file_cases/indentation_cases.rb +105 -54
  15. data/features/support/file_cases/naming_cases.rb +0 -1
  16. data/features/support/file_cases/vertical_spacing_cases.rb +0 -1
  17. data/features/support/legacy/bad_ternary_colon_spacing.rb +1 -1
  18. data/features/valid_ruby.feature +17 -0
  19. data/features/vertical_spacing.feature +40 -19
  20. data/lib/ext/string_ext.rb +12 -0
  21. data/lib/tailor/cli.rb +7 -5
  22. data/lib/tailor/cli/options.rb +13 -3
  23. data/lib/tailor/composite_observable.rb +17 -2
  24. data/lib/tailor/configuration.rb +83 -72
  25. data/lib/tailor/configuration/style.rb +85 -0
  26. data/lib/tailor/critic.rb +67 -117
  27. data/lib/tailor/formatter.rb +38 -0
  28. data/lib/tailor/formatters/text.rb +35 -10
  29. data/lib/tailor/lexed_line.rb +38 -5
  30. data/lib/tailor/lexer.rb +150 -14
  31. data/lib/tailor/{lexer_constants.rb → lexer/lexer_constants.rb} +9 -7
  32. data/lib/tailor/lexer/token.rb +6 -2
  33. data/lib/tailor/logger.rb +4 -0
  34. data/lib/tailor/problem.rb +8 -73
  35. data/lib/tailor/reporter.rb +1 -1
  36. data/lib/tailor/ruler.rb +67 -6
  37. data/lib/tailor/rulers/allow_camel_case_methods_ruler.rb +9 -1
  38. data/lib/tailor/rulers/allow_hard_tabs_ruler.rb +9 -1
  39. data/lib/tailor/rulers/allow_invalid_ruby_ruler.rb +38 -0
  40. data/lib/tailor/rulers/allow_screaming_snake_case_classes_ruler.rb +9 -2
  41. data/lib/tailor/rulers/allow_trailing_line_spaces_ruler.rb +10 -5
  42. data/lib/tailor/rulers/indentation_spaces_ruler.rb +93 -26
  43. data/lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb +128 -84
  44. data/lib/tailor/rulers/max_code_lines_in_class_ruler.rb +9 -5
  45. data/lib/tailor/rulers/max_code_lines_in_method_ruler.rb +9 -5
  46. data/lib/tailor/rulers/max_line_length_ruler.rb +10 -5
  47. data/lib/tailor/rulers/spaces_after_comma_ruler.rb +13 -4
  48. data/lib/tailor/rulers/spaces_after_lbrace_ruler.rb +8 -4
  49. data/lib/tailor/rulers/spaces_after_lbracket_ruler.rb +8 -4
  50. data/lib/tailor/rulers/spaces_after_lparen_ruler.rb +8 -4
  51. data/lib/tailor/rulers/spaces_before_comma_ruler.rb +8 -4
  52. data/lib/tailor/rulers/spaces_before_lbrace_ruler.rb +13 -6
  53. data/lib/tailor/rulers/spaces_before_rbrace_ruler.rb +12 -8
  54. data/lib/tailor/rulers/spaces_before_rbracket_ruler.rb +12 -5
  55. data/lib/tailor/rulers/spaces_before_rparen_ruler.rb +13 -6
  56. data/lib/tailor/rulers/spaces_in_empty_braces_ruler.rb +13 -9
  57. data/lib/tailor/rulers/trailing_newlines_ruler.rb +10 -5
  58. data/lib/tailor/tailorrc.erb +3 -3
  59. data/lib/tailor/version.rb +1 -1
  60. data/m.rb +15 -0
  61. data/spec/spec_helper.rb +0 -1
  62. data/spec/tailor/cli_spec.rb +8 -9
  63. data/spec/tailor/composite_observable_spec.rb +41 -0
  64. data/spec/tailor/configuration/style_spec.rb +197 -0
  65. data/spec/tailor/configuration_spec.rb +52 -33
  66. data/spec/tailor/critic_spec.rb +7 -8
  67. data/spec/tailor/formatter_spec.rb +52 -0
  68. data/spec/tailor/lexed_line_spec.rb +236 -88
  69. data/spec/tailor/lexer_spec.rb +8 -63
  70. data/spec/tailor/problem_spec.rb +14 -46
  71. data/spec/tailor/reporter_spec.rb +8 -8
  72. data/spec/tailor/ruler_spec.rb +1 -1
  73. data/spec/tailor/rulers/indentation_spaces_ruler/indentation_manager_spec.rb +132 -176
  74. data/spec/tailor/rulers/indentation_spaces_ruler_spec.rb +41 -33
  75. data/spec/tailor/rulers/{spaces_after_comma_spec.rb → spaces_after_comma_ruler_spec.rb} +5 -5
  76. data/spec/tailor/rulers/spaces_after_lbrace_ruler_spec.rb +14 -14
  77. data/spec/tailor/rulers/spaces_before_lbrace_ruler_spec.rb +1 -1
  78. data/spec/tailor/rulers/spaces_before_rbrace_ruler_spec.rb +1 -1
  79. data/spec/tailor/version_spec.rb +1 -1
  80. data/spec/tailor_spec.rb +3 -1
  81. data/tailor.gemspec +11 -3
  82. data/uest.rb +9 -0
  83. metadata +66 -41
  84. data/features/step_definitions/spacing/commas_steps.rb +0 -14
@@ -1,11 +1,13 @@
1
+ require 'set'
2
+
1
3
  class Tailor
2
4
 
3
5
  # These are important tokens that key certain styling events. They are taken
4
6
  # from:
5
- # https://github.com/ruby/ruby/blob/trunk/ext/ripper/eventids2.c
6
- # https://github.com/ruby/ruby/blob/trunk/parse.y
7
+ # * https://github.com/ruby/ruby/blob/trunk/ext/ripper/eventids2.c
8
+ # * https://github.com/ruby/ruby/blob/trunk/parse.y
7
9
  module LexerConstants
8
- KEYWORDS_TO_INDENT = [
10
+ KEYWORDS_TO_INDENT = Set.new [
9
11
  'begin',
10
12
  'case',
11
13
  'class',
@@ -24,7 +26,7 @@ class Tailor
24
26
  'while'
25
27
  ]
26
28
 
27
- CONTINUATION_KEYWORDS = [
29
+ CONTINUATION_KEYWORDS = Set.new [
28
30
  'elsif',
29
31
  'else',
30
32
  'ensure',
@@ -32,7 +34,7 @@ class Tailor
32
34
  'when'
33
35
  ]
34
36
 
35
- KEYWORDS_AND_MODIFIERS = [
37
+ KEYWORDS_AND_MODIFIERS = Set.new [
36
38
  'if',
37
39
  'unless',
38
40
  'until',
@@ -47,7 +49,7 @@ class Tailor
47
49
  'while' => :while_mod
48
50
  }
49
51
 
50
- MULTILINE_OPERATORS = [
52
+ MULTILINE_OPERATORS = Set.new [
51
53
  '+', '-', '*', '**', '/', '%', # +, -, tSTAR, tPOW, /, %
52
54
  '<', '>', '<=', '>=', # <, >, tLEQ, tGEQ
53
55
  '=', '+=', '-=', '*=', '**=', '/=', '%=',
@@ -66,7 +68,7 @@ class Tailor
66
68
  '~>' # gem_version op
67
69
  ]
68
70
 
69
- LOOP_KEYWORDS = [
71
+ LOOP_KEYWORDS = Set.new [
70
72
  'for',
71
73
  'until',
72
74
  'while'
@@ -1,13 +1,17 @@
1
1
  require 'ripper'
2
- require_relative '../lexer_constants'
2
+ require_relative 'lexer_constants'
3
3
  require_relative '../logger'
4
4
 
5
5
  class Tailor
6
6
  class Lexer < ::Ripper::Lexer
7
+
8
+ # Helper methods for tokens that are parsed by {Tailor::Lexer}.
7
9
  class Token < String
8
10
  include LexerConstants
9
11
  include Tailor::Logger::Mixin
10
12
 
13
+ # @param [String] the_token
14
+ # @param [Hash] options
11
15
  def initialize(the_token, options={})
12
16
  super(the_token)
13
17
  @options = options
@@ -78,7 +82,7 @@ class Tailor
78
82
 
79
83
  if sexp_line.nil?
80
84
  log "sexp line was nil again."
81
- log "Trying one more time with the last char removed from the line..."
85
+ log "Trying 1 more time with the last char removed from the line..."
82
86
  line_of_text.chop!
83
87
  sexp_line = Ripper.sexp(line_of_text)
84
88
  end
data/lib/tailor/logger.rb CHANGED
@@ -4,6 +4,8 @@ class Tailor
4
4
  class Logger
5
5
  extend LogSwitch
6
6
 
7
+ # Overrides the LogSwitch Logger to custom format the time format in log
8
+ # messages.
7
9
  def self.logger
8
10
  return @logger if @logger
9
11
  @logger ||= ::Logger.new $stdout
@@ -15,6 +17,8 @@ class Tailor
15
17
  @logger
16
18
  end
17
19
 
20
+ # Provides an .included hook to insert the name of the class for each log
21
+ # message in the class that includes the Mixin.
18
22
  module Mixin
19
23
  def self.included(base)
20
24
  define_method :log do |*args|
@@ -3,22 +3,23 @@ require_relative 'runtime_error'
3
3
 
4
4
  class Tailor
5
5
 
6
- # A Hashed data structure that abstracts out data (especially the error
7
- # message) to build reports from.
6
+ # A Hashed data structure that simply defines the data needed to report a
7
+ # problem
8
8
  class Problem < Hash
9
9
  include LogSwitch::Mixin
10
10
 
11
11
  # @param [Symbol] type The problem type.
12
12
  # @param [Binding] binding The context that the problem was discovered in.
13
- def initialize(type, line, column, options={})
13
+ def initialize(type, line, column, message, level)
14
14
  @type = type
15
15
  @line = line
16
16
  @column = column
17
- @options = options
17
+ @message = message
18
+ @level = level
18
19
  set_values
19
20
  subclass_name = self.class.to_s.sub(/^Tailor::/, '')
20
21
  msg = "<#{subclass_name}> #{self[:line]}[#{self[:column]}]: "
21
- msg << "ERROR[:#{self[:type]}] #{self[:message]}"
22
+ msg << "#{@level.upcase}[:#{self[:type]}] #{self[:message]}"
22
23
  log msg
23
24
  end
24
25
 
@@ -27,74 +28,8 @@ class Tailor
27
28
  self[:type] = @type
28
29
  self[:line] = @line
29
30
  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
31
+ self[:message] = @message
32
+ self[:level] = @level
98
33
  end
99
34
  end
100
35
  end
@@ -15,7 +15,7 @@ class Tailor
15
15
 
16
16
  formats.flatten.each do |formatter|
17
17
  require_relative "formatters/#{formatter}"
18
- @formatters << eval("Tailor::Formatter::#{formatter.capitalize}.new")
18
+ @formatters << eval("Tailor::Formatters::#{formatter.capitalize}.new")
19
19
  end
20
20
  end
21
21
 
data/lib/tailor/ruler.rb CHANGED
@@ -1,27 +1,72 @@
1
1
  require_relative 'logger'
2
2
  require_relative 'problem'
3
3
  require_relative 'runtime_error'
4
+ require_relative '../ext/string_ext'
4
5
 
5
6
  class Tailor
7
+
8
+ # This is a composite class, geared for getting at or managing the Rulers
9
+ # that should be used for measuring style. To do so, create a new object of
10
+ # this type, then add child rulers to that object using +#add_child_ruler+.
11
+ # After using the Ruler, you'll have access to all of the problems found by
12
+ # all of the child rulers via +#problems+.
13
+ #
14
+ # Example:
15
+ # ruler = Ruler.new
16
+ # file_length_ruler = FileLengthRuler.new
17
+ # ruler.add_child_ruler(file_length_ruler)
18
+ # # use ruler
19
+ # ruler.problems # => [{ all of your file length problems... }]
20
+ #
21
+ # There's really no measuring functionality in this base class--it's merely
22
+ # for aggregating child data and for providing a base class to create child
23
+ # Rulers from. Speaking of... if you want, you can create your own rulers.
24
+ # A Ruler requires a few things:
25
+ #
26
+ # First, it needs a list of Lexer events to observer. Tailor uses its Lexer
27
+ # to publish events (in this case, characters or string Ruby constructs) of
28
+ # interest to its observers. Rulers subscribe to those events so that they
29
+ # can detect the problems they're looking for. These are defined as a Set in
30
+ # @lexer_observers. Adding to that list means the Ruler will subscribe to
31
+ # those events.
32
+ #
33
+ # Example:
34
+ # class MyRuler < Tailor::Ruler
35
+ # def initialize
36
+ # add_lexer_observers = :nl_observer, :kw_observer
37
+ # end
38
+ # end
6
39
  class Ruler
7
40
  include Tailor::Logger::Mixin
8
41
 
9
- attr_reader :cli_option
42
+ attr_reader :lexer_observers
43
+ attr_reader :level
10
44
 
11
- def initialize(config={})
45
+ # @param [Object] config
46
+ # @param [Hash] options
47
+ def initialize(config=nil, options={ level: :error })
12
48
  @config = config
13
- @problems = []
14
- @child_rulers = []
15
- @cli_option = ""
49
+ @options = options
16
50
  @do_measurement = true
17
51
  log "Ruler initialized with style setting: #{@config}"
52
+ log "Ruler initialized with problem level setting: #{@options[:level]}"
53
+
54
+ @child_rulers = []
55
+ @lexer_observers = []
56
+ @problems = []
18
57
  end
19
58
 
59
+ # Adds the {Tailor::Ruler} object to the list of child rulers.
60
+ #
61
+ # @param [Tailor::Ruler] ruler
20
62
  def add_child_ruler(ruler)
21
63
  @child_rulers << ruler
22
- log "Added child: #{ruler}"
64
+ log "Added child ruler: #{ruler}"
23
65
  end
24
66
 
67
+ # Gets all of the problems from all child rulers.
68
+ #
69
+ # @return [Array] The list of problems.
25
70
  def problems
26
71
  @problems = @child_rulers.inject(@problems) do |problems, ruler|
27
72
  problems + ruler.problems
@@ -35,5 +80,21 @@ class Tailor
35
80
  raise RuntimeError,
36
81
  "Ruler#measure called, but should be redefined by a real ruler."
37
82
  end
83
+
84
+ # Converts the {Tailor::Ruler} name to snake case.
85
+ #
86
+ # @return [String] The ruler name as snake-case that represents the problem
87
+ # that was found.
88
+ def problem_type
89
+ self.class.to_s =~ /^.+::(\S+)Ruler$/
90
+
91
+ $1.underscore
92
+ end
93
+
94
+ private
95
+
96
+ def add_lexer_observers(*lexer_observer)
97
+ @lexer_observers = lexer_observer
98
+ end
38
99
  end
39
100
  end
@@ -3,6 +3,11 @@ require_relative '../ruler'
3
3
  class Tailor
4
4
  module Rulers
5
5
  class AllowCamelCaseMethodsRuler < Tailor::Ruler
6
+ def initialize(style, options)
7
+ super(style, options)
8
+ add_lexer_observers :ident
9
+ end
10
+
6
11
  def ident_update(token, lexed_line, lineno, column)
7
12
  ident_index = lexed_line.event_index(column)
8
13
  previous_event = lexed_line.event_at(ident_index - 2)
@@ -22,7 +27,10 @@ class Tailor
22
27
  # @param [Fixnum] column Column the problem was found on.
23
28
  def measure(token, lineno, column)
24
29
  if token.contains_capital_letter?
25
- @problems << Problem.new(:camel_case_method, lineno, column)
30
+ problem_message = "Camel-case method name found."
31
+
32
+ @problems << Problem.new(problem_type, lineno, column,
33
+ problem_message, @options[:level])
26
34
  end
27
35
  end
28
36
  end
@@ -3,6 +3,11 @@ require_relative '../ruler'
3
3
  class Tailor
4
4
  module Rulers
5
5
  class AllowHardTabsRuler < Tailor::Ruler
6
+ def initialize(config, options)
7
+ super(config, options)
8
+ add_lexer_observers :sp
9
+ end
10
+
6
11
  def sp_update(token, lineno, column)
7
12
  measure(token, lineno, column)
8
13
  end
@@ -14,7 +19,10 @@ class Tailor
14
19
  # @param [Fixnum] column Column the problem was found on.
15
20
  def measure(token, lineno, column)
16
21
  if token.contains_hard_tab?
17
- @problems << Problem.new(:hard_tab, lineno, column)
22
+ problem_message = "Hard tab found."
23
+
24
+ @problems << Problem.new(problem_type, lineno, column,
25
+ problem_message, @options[:level])
18
26
  end
19
27
  end
20
28
  end
@@ -0,0 +1,38 @@
1
+ require_relative '../ruler'
2
+
3
+
4
+ class Tailor
5
+ module Rulers
6
+ class AllowInvalidRubyRuler < Tailor::Ruler
7
+ def initialize(config, options)
8
+ super(config, options)
9
+ add_lexer_observers :file_beg
10
+ end
11
+
12
+ def file_beg_update(file_name)
13
+ @file_name = file_name
14
+ measure
15
+ end
16
+
17
+ # @return [Boolean]
18
+ def invalid_ruby?
19
+ log "Checking for valid Ruby..."
20
+ result = `ruby -c #{@file_name}`
21
+
22
+ result.size.zero?
23
+ end
24
+
25
+ def measure
26
+ if invalid_ruby? && @config == false
27
+ lineno = 0
28
+ column = 0
29
+ msg = "File contains invalid Ruby; run `ruby -c [your_file.rb]` "
30
+ msg << "for more details."
31
+
32
+ @problems << Problem.new(problem_type, lineno, column, msg,
33
+ @options[:level])
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -3,6 +3,11 @@ require_relative '../ruler'
3
3
  class Tailor
4
4
  module Rulers
5
5
  class AllowScreamingSnakeCaseClassesRuler < Tailor::Ruler
6
+ def initialize(config, options)
7
+ super(config, options)
8
+ add_lexer_observers :const
9
+ end
10
+
6
11
  def const_update(token, lexed_line, lineno, column)
7
12
  ident_index = lexed_line.event_index(column)
8
13
  previous_event = lexed_line.event_at(ident_index - 2)
@@ -23,8 +28,10 @@ class Tailor
23
28
  # @param [Fixnum] column Column the potential problem is on.
24
29
  def measure(token, lineno, column)
25
30
  if token.screaming_snake_case?
26
- @problems << Problem.new(:screaming_snake_case_class_name,
27
- lineno, column)
31
+ problem_message = "Screaming-snake-case class/module found."
32
+
33
+ @problems << Problem.new(problem_type, lineno, column,
34
+ problem_message, @options[:level])
28
35
  end
29
36
  end
30
37
  end
@@ -3,6 +3,11 @@ require_relative '../ruler'
3
3
  class Tailor
4
4
  module Rulers
5
5
  class AllowTrailingLineSpacesRuler < Tailor::Ruler
6
+ def initialize(config, options)
7
+ super(config, options)
8
+ add_lexer_observers :ignored_nl, :nl
9
+ end
10
+
6
11
  def ignored_nl_update(lexed_line, lineno, column)
7
12
  log "Last event: #{lexed_line.last_non_line_feed_event}"
8
13
  log "Line ends with space: #{lexed_line.ends_with_sp?}"
@@ -21,11 +26,11 @@ class Tailor
21
26
  # @param [Fixnum] column Column the potential problem is on.
22
27
  def measure(lexed_line, lineno, column)
23
28
  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
+ actual = lexed_line.last_non_line_feed_event.last.size
30
+ problem_message = "Line has #{actual} trailing spaces."
31
+
32
+ @problems << Problem.new(problem_type, lineno, column,
33
+ problem_message, @options[:level])
29
34
  end
30
35
  end
31
36
  end