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
@@ -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