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
@@ -0,0 +1,85 @@
1
+ class Tailor
2
+ class Configuration
3
+ class Style
4
+
5
+ # Adds a style property to a Style object. If you're planning on creating
6
+ # your own {Ruler}, you need to register the property here.
7
+ #
8
+ # Defines a method from +name+ that takes 2 parameters: +value+ and
9
+ # +options+. +value+ is the value to use for the {Ruler} of the same +name+
10
+ # for checking style. +options+ can include anything that's necessary for
11
+ # style checking. A +:level+ option key is used to determine the
12
+ # {Tailor::Problem} level:
13
+ # * +:error+ results in a exit status of 1.
14
+ # * +:warn+ results in an exit status of 0, but gets printed in the
15
+ # report.
16
+ #
17
+ # Example:
18
+ # Tailor::Configuration::Style.define_property(:my_style_property)
19
+ # style = Tailor::Configuration::Style.new
20
+ # style.my_style_property(100, level: :warn)
21
+ def self.define_property(name)
22
+ define_method(name) do |value, *options|
23
+ options = options.first || { level: :error }
24
+ instance_variable_set("@#{name}".to_sym, [value, options])
25
+ end
26
+ end
27
+
28
+ define_property :allow_camel_case_methods
29
+ define_property :allow_hard_tabs
30
+ define_property :allow_screaming_snake_case_classes
31
+ define_property :allow_trailing_line_spaces
32
+ define_property :allow_invalid_ruby
33
+ define_property :indentation_spaces
34
+ define_property :max_code_lines_in_class
35
+ define_property :max_code_lines_in_method
36
+ define_property :max_line_length
37
+ define_property :spaces_after_comma
38
+ define_property :spaces_after_lbrace
39
+ define_property :spaces_after_lbracket
40
+ define_property :spaces_after_lparen
41
+ define_property :spaces_before_comma
42
+ define_property :spaces_before_lbrace
43
+ define_property :spaces_before_rbrace
44
+ define_property :spaces_before_rbracket
45
+ define_property :spaces_before_rparen
46
+ define_property :spaces_in_empty_braces
47
+ define_property :trailing_newlines
48
+
49
+ # Sets up default values.
50
+ def initialize
51
+ allow_camel_case_methods(false, level: :error)
52
+ allow_hard_tabs(false, level: :error)
53
+ allow_screaming_snake_case_classes(false, level: :error)
54
+ allow_trailing_line_spaces(false, level: :error)
55
+ allow_invalid_ruby(false, level: :warn)
56
+ indentation_spaces(2, level: :error)
57
+ max_code_lines_in_class(300, level: :error)
58
+ max_code_lines_in_method(30, level: :error)
59
+ max_line_length(80, level: :error)
60
+ spaces_after_comma(1, level: :error)
61
+ spaces_after_lbrace(1, level: :error)
62
+ spaces_after_lbracket(0, level: :error)
63
+ spaces_after_lparen(0, level: :error)
64
+ spaces_before_comma(0, level: :error)
65
+ spaces_before_lbrace(1, level: :error)
66
+ spaces_before_rbrace(1, level: :error)
67
+ spaces_before_rbracket(0, level: :error)
68
+ spaces_before_rparen(0, level: :error)
69
+ spaces_in_empty_braces(0, level: :error)
70
+ trailing_newlines(1, level: :error)
71
+ end
72
+
73
+ # Returns the current style as a Hash.
74
+ #
75
+ # @return [Hash]
76
+ def to_hash
77
+ instance_variables.inject({}) do |result, ivar|
78
+ result[ivar.to_s.sub(/@/, '').to_sym] = instance_variable_get(ivar)
79
+
80
+ result
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
data/lib/tailor/critic.rb CHANGED
@@ -1,137 +1,57 @@
1
- require 'erb'
2
- require 'yaml'
3
- require 'fileutils'
4
- require_relative 'configuration'
5
1
  require_relative 'lexer'
6
2
  require_relative 'logger'
7
3
  require_relative 'ruler'
8
4
  require_relative 'rulers'
9
- require_relative 'runtime_error'
10
5
 
11
6
 
12
7
  class Tailor
8
+
9
+ # An object of this type provides for starting the process of critiquing
10
+ # files. It handles initializing the Ruler objects it needs based on the
11
+ # configuration given to it.
13
12
  class Critic
14
13
  include Tailor::Logger::Mixin
15
14
  include Tailor::Rulers
16
15
 
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
16
+ # The instance method that starts the process of looking for problems in
17
+ # files. It checks for problems in each file in each file set. It yields
18
+ # the problems found and the label they were found for.
19
+ #
20
+ # @param [Hash] file_sets The file sets to critique.
21
+ # @yieldparam [Hash] problems The problems found for the label.
22
+ # @yieldparam [Symbol] label The label the problems were found for.
23
+ def critique(file_sets)
24
+ log "file sets keys: #{file_sets.keys}"
98
25
 
99
- def critique
100
- log "file sets keys: #{@file_sets.keys}"
101
- @file_sets.each do |label, file_set|
26
+ file_sets.each do |label, file_set|
102
27
  log "Critiquing file_set: #{file_set}"
103
28
 
104
29
  file_set[:file_list].each do |file|
105
30
  log "Critiquing file: #{file}"
106
- problems = check_file(file, file_set[:style])
31
+
32
+ begin
33
+ problems = check_file(file, file_set[:style])
34
+ rescue => ex
35
+ $stderr.puts "Error while parsing file #{file}"
36
+ raise(ex)
37
+ end
38
+
107
39
  yield [problems, label] if block_given?
108
40
  end
109
41
  end
110
42
  end
111
43
 
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
44
+ # @return [Hash]
45
+ def problems
46
+ @problems ||= {}
124
47
  end
125
48
 
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
49
+ # @return [Fixnum] The number of problems found so far.
50
+ def problem_count
51
+ problems.values.flatten.size
132
52
  end
133
53
 
134
- # Adds problems found from Lexing to the {problems} list.
54
+ # Adds problems found from Lexing to the +#problems+ list.
135
55
  #
136
56
  # @param [String] file The file to open, read, and check.
137
57
  # @return [Hash] The Problems for that file.
@@ -139,24 +59,54 @@ class Tailor
139
59
  log "<#{self.class}> Checking style of file: #{file}."
140
60
  lexer = Tailor::Lexer.new(file)
141
61
  ruler = Ruler.new
142
- log "Style: #{style}"
62
+ log "Style:"
63
+ style.each { |property, values| log "#{property}: #{values}" }
143
64
  init_rulers(style, lexer, ruler)
144
65
 
145
66
  lexer.lex
146
- lexer.check_added_newline
147
67
  problems[file] = ruler.problems
148
68
 
149
69
  { file => problems[file] }
150
70
  end
151
71
 
152
- # @return [Hash]
153
- def problems
154
- @problems ||= {}
72
+ private
73
+
74
+ # Creates Rulers for each ruler given in +style+ and adds the Ruler's
75
+ # defined observers to the given +lexer+.
76
+ #
77
+ # @param [Hash] style The Hash that defines the style to be measured
78
+ # against.
79
+ # @param [Tailor::Lexer] lexer The Lexer object the Rulers should observe.
80
+ # @param [Tailor::Ruler] parent_ruler The main Ruler to add the child
81
+ # Rulers to.
82
+ def init_rulers(style, lexer, parent_ruler)
83
+ style.each do |ruler_name, values|
84
+ ruler = "Tailor::Rulers::#{camelize(ruler_name.to_s)}Ruler"
85
+
86
+ if values.last[:level] == :off || values.last[:level] == "off"
87
+ msg = "Style option set to '#{values.last[:level]}'; "
88
+ log msg << "skipping init of '#{ruler}'"
89
+ next
90
+ end
91
+
92
+ log "Initializing ruler: #{ruler}"
93
+ ruler = instance_eval("#{ruler}.new(#{values.first}, #{values.last})")
94
+ parent_ruler.add_child_ruler(ruler)
95
+
96
+ ruler.lexer_observers.each do |observer|
97
+ log "Adding #{observer} to lexer..."
98
+ meth = "add_#{observer}_observer".to_sym
99
+ lexer.send(meth, ruler)
100
+ end
101
+ end
155
102
  end
156
103
 
157
- # @return [Fixnum] The number of problems found so far.
158
- def problem_count
159
- problems.values.flatten.size
104
+ # Converts a snake-case String to a camel-case String.
105
+ #
106
+ # @param [String] string The String to convert.
107
+ # @return [String] The converted String.
108
+ def camelize(string)
109
+ string.split(/_/).map { |word| word.capitalize }.join
160
110
  end
161
111
  end
162
112
  end
@@ -0,0 +1,38 @@
1
+ class Tailor
2
+
3
+ # This is really just a base class for defining other Formatter types.
4
+ class Formatter
5
+ def initialize
6
+ @pwd = Pathname(Dir.pwd)
7
+ end
8
+
9
+ # This method gets called by {Tailor::Reporter} after each file is
10
+ # critiqued. Redefine this to do what you want for that part of your
11
+ # report.
12
+ def file_report(file_problems, label)
13
+ # Redefine this for your formatter...
14
+ end
15
+
16
+ # This method gets called by {Tailor::Reporter} after all files are
17
+ # critiqued. Redefine this to do what you want for that part of your
18
+ # report.
19
+ def summary_report(all_problems)
20
+ # Redefine this for your formatter...
21
+ end
22
+
23
+ # @param [Hash<Array>] problems
24
+ # @param [Symbol] level The level of problem to find.
25
+ # @return [Array] Problem list at the given level.
26
+ def problems_at_level(problems, level)
27
+ problems.values.flatten.find_all { |v| v[:level] == level }
28
+ end
29
+
30
+ # Gets a list of all types of problems included in the problem set.
31
+ #
32
+ # @param [Array] problems
33
+ # @return [Array<Symbol>] The list of problem types.
34
+ def problem_levels(problems)
35
+ problems.values.flatten.collect { |v| v[:level] }.uniq
36
+ end
37
+ end
38
+ end
@@ -1,13 +1,23 @@
1
1
  require 'text-table'
2
+ require 'pathname'
3
+ require_relative '../formatter'
2
4
 
3
5
  class Tailor
4
- module Formatter
5
- class Text
6
+ module Formatters
7
+ class Text < Tailor::Formatter
8
+ PROBLEM_LEVEL_COLORS = {
9
+ error: :red,
10
+ warn: :yellow
11
+ }
12
+
13
+ # @return [String] A line of "#-----", with length determined by +length+.
6
14
  def line(length=79)
7
15
  "##{'-' * length}\n"
8
16
  end
9
17
 
18
+ # @return [String] The portion of the header that displays the file info.
10
19
  def file_header(file)
20
+ file = Pathname(file)
11
21
  message = ""
12
22
  message << if defined? Term::ANSIColor
13
23
  "# #{'File:'.underscore}\n"
@@ -15,12 +25,14 @@ class Tailor
15
25
  "# File:\n"
16
26
  end
17
27
 
18
- message << "# #{file}\n"
28
+ message << "# #{file.relative_path_from(@pwd)}\n"
19
29
  message << "#\n"
20
30
 
21
31
  message
22
32
  end
23
33
 
34
+ # @return [String] The portion of the header that displays the file_set
35
+ # label info.
24
36
  def file_set_header(file_set)
25
37
  message = ""
26
38
  message << if defined? Term::ANSIColor
@@ -35,6 +47,8 @@ class Tailor
35
47
  message
36
48
  end
37
49
 
50
+ # @return [String] The portion of the report that displays all of the
51
+ # problems for the file.
38
52
  def problems_header(problem_list)
39
53
  message = ""
40
54
  message << if defined? Term::ANSIColor
@@ -44,12 +58,14 @@ class Tailor
44
58
  end
45
59
 
46
60
  problem_list.each_with_index do |problem, i|
61
+ color = PROBLEM_LEVEL_COLORS[problem[:level]] || :white
62
+
47
63
  position = if problem[:line] == '<EOF>'
48
64
  '<EOF>'
49
65
  else
50
66
  if defined? Term::ANSIColor
51
- msg = "#{problem[:line].to_s.red.bold}:"
52
- msg << "#{problem[:column].to_s.red.bold}"
67
+ msg = "#{problem[:line].to_s.send(color).bold}:"
68
+ msg << "#{problem[:column].to_s.send(color).bold}"
53
69
  msg
54
70
  else
55
71
  "#{problem[:line]}:#{problem[:column]}"
@@ -59,8 +75,8 @@ class Tailor
59
75
  message << if defined? Term::ANSIColor
60
76
  %Q{# #{(i + 1).to_s.bold}.
61
77
  # * position: #{position}
62
- # * type: #{problem[:type].to_s.red}
63
- # * message: #{problem[:message].red}
78
+ # * property: #{problem[:type].to_s.send(color)}
79
+ # * message: #{problem[:message].send(color)}
64
80
  }
65
81
  else
66
82
  %Q{# #{(i + 1)}.
@@ -111,12 +127,21 @@ class Tailor
111
127
  summary_table.rows << :separator
112
128
 
113
129
  problems.each do |file, problem_list|
114
- summary_table.rows << [file, problem_list.size]
130
+ file = Pathname(file)
131
+ summary_table.rows << [
132
+ file.relative_path_from(@pwd), problem_list.size
133
+ ]
134
+ end
135
+
136
+ summary_table.rows << :separator
137
+
138
+ problem_levels(problems).inject(summary_table.rows) do |result, level|
139
+ result << [level.capitalize,
140
+ problems_at_level(problems, level).size]
115
141
  end
116
142
 
117
143
  summary_table.rows << :separator
118
- summary_table.rows << ['TOTAL', problems.values.
119
- map { |v| v.size }.inject(:+)]
144
+ summary_table.rows << ['TOTAL', problems.values.flatten.size]
120
145
 
121
146
  puts summary_table
122
147
  end