tailor 1.0.0.alpha2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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