rubocop 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubocop might be problematic. Click here for more details.

Files changed (149) hide show
  1. data/.travis.yml +3 -1
  2. data/CHANGELOG.md +38 -0
  3. data/README.md +34 -0
  4. data/Rakefile +3 -0
  5. data/config/default.yml +14 -1
  6. data/config/enabled.yml +30 -7
  7. data/lib/rubocop.rb +15 -0
  8. data/lib/rubocop/cli.rb +48 -154
  9. data/lib/rubocop/config.rb +19 -22
  10. data/lib/rubocop/config_store.rb +2 -4
  11. data/lib/rubocop/cop/commissioner.rb +90 -0
  12. data/lib/rubocop/cop/cop.rb +38 -31
  13. data/lib/rubocop/cop/corrector.rb +84 -0
  14. data/lib/rubocop/cop/lint/assignment_in_condition.rb +0 -3
  15. data/lib/rubocop/cop/lint/block_alignment.rb +151 -0
  16. data/lib/rubocop/cop/lint/empty_ensure.rb +18 -0
  17. data/lib/rubocop/cop/lint/end_alignment.rb +0 -124
  18. data/lib/rubocop/cop/lint/end_in_method.rb +0 -2
  19. data/lib/rubocop/cop/lint/ensure_return.rb +3 -3
  20. data/lib/rubocop/cop/lint/eval.rb +0 -2
  21. data/lib/rubocop/cop/lint/handle_exceptions.rb +0 -2
  22. data/lib/rubocop/cop/lint/literal_in_condition.rb +0 -10
  23. data/lib/rubocop/cop/lint/loop.rb +0 -2
  24. data/lib/rubocop/cop/lint/rescue_exception.rb +0 -2
  25. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +2 -2
  26. data/lib/rubocop/cop/lint/unreachable_code.rb +0 -2
  27. data/lib/rubocop/cop/lint/unused_local_variable.rb +2 -2
  28. data/lib/rubocop/cop/lint/void.rb +0 -2
  29. data/lib/rubocop/cop/offence.rb +9 -0
  30. data/lib/rubocop/cop/rails/validation.rb +2 -1
  31. data/lib/rubocop/cop/style/access_control.rb +4 -3
  32. data/lib/rubocop/cop/style/alias.rb +2 -4
  33. data/lib/rubocop/cop/style/align_parameters.rb +0 -2
  34. data/lib/rubocop/cop/style/and_or.rb +4 -6
  35. data/lib/rubocop/cop/style/ascii_comments.rb +2 -2
  36. data/lib/rubocop/cop/style/ascii_identifiers.rb +2 -2
  37. data/lib/rubocop/cop/style/attr.rb +0 -2
  38. data/lib/rubocop/cop/style/avoid_class_vars.rb +0 -1
  39. data/lib/rubocop/cop/style/avoid_for.rb +0 -2
  40. data/lib/rubocop/cop/style/avoid_global_vars.rb +3 -7
  41. data/lib/rubocop/cop/style/avoid_perl_backrefs.rb +0 -2
  42. data/lib/rubocop/cop/style/avoid_perlisms.rb +2 -4
  43. data/lib/rubocop/cop/style/begin_block.rb +0 -2
  44. data/lib/rubocop/cop/style/block_comments.rb +2 -2
  45. data/lib/rubocop/cop/style/block_nesting.rb +3 -3
  46. data/lib/rubocop/cop/style/blocks.rb +0 -2
  47. data/lib/rubocop/cop/style/case_equality.rb +0 -2
  48. data/lib/rubocop/cop/style/case_indentation.rb +0 -2
  49. data/lib/rubocop/cop/style/character_literal.rb +10 -6
  50. data/lib/rubocop/cop/style/class_and_module_camel_case.rb +0 -4
  51. data/lib/rubocop/cop/style/class_methods.rb +1 -1
  52. data/lib/rubocop/cop/style/collection_methods.rb +3 -5
  53. data/lib/rubocop/cop/style/colon_method_call.rb +3 -3
  54. data/lib/rubocop/cop/style/comment_annotation.rb +44 -0
  55. data/lib/rubocop/cop/style/constant_name.rb +0 -2
  56. data/lib/rubocop/cop/style/def_parentheses.rb +0 -8
  57. data/lib/rubocop/cop/style/documentation.rb +6 -2
  58. data/lib/rubocop/cop/style/dot_position.rb +0 -2
  59. data/lib/rubocop/cop/style/empty_line_between_defs.rb +0 -2
  60. data/lib/rubocop/cop/style/empty_lines.rb +10 -8
  61. data/lib/rubocop/cop/style/empty_literal.rb +3 -1
  62. data/lib/rubocop/cop/style/encoding.rb +7 -6
  63. data/lib/rubocop/cop/style/end_block.rb +0 -2
  64. data/lib/rubocop/cop/style/end_of_line.rb +4 -3
  65. data/lib/rubocop/cop/style/favor_join.rb +0 -2
  66. data/lib/rubocop/cop/style/favor_modifier.rb +9 -9
  67. data/lib/rubocop/cop/style/favor_sprintf.rb +0 -2
  68. data/lib/rubocop/cop/style/favor_unless_over_negated_if.rb +0 -2
  69. data/lib/rubocop/cop/style/hash_syntax.rb +0 -2
  70. data/lib/rubocop/cop/style/if_then_else.rb +0 -2
  71. data/lib/rubocop/cop/style/lambda.rb +0 -2
  72. data/lib/rubocop/cop/style/leading_comment_space.rb +2 -2
  73. data/lib/rubocop/cop/style/line_continuation.rb +4 -3
  74. data/lib/rubocop/cop/style/line_length.rb +4 -3
  75. data/lib/rubocop/cop/style/method_and_variable_snake_case.rb +4 -3
  76. data/lib/rubocop/cop/style/method_call_parentheses.rb +0 -2
  77. data/lib/rubocop/cop/style/method_length.rb +0 -4
  78. data/lib/rubocop/cop/style/not.rb +0 -2
  79. data/lib/rubocop/cop/style/op_method.rb +0 -2
  80. data/lib/rubocop/cop/style/parameter_lists.rb +0 -2
  81. data/lib/rubocop/cop/style/parentheses_around_condition.rb +12 -6
  82. data/lib/rubocop/cop/style/proc.rb +0 -2
  83. data/lib/rubocop/cop/style/reduce_arguments.rb +0 -2
  84. data/lib/rubocop/cop/style/redundant_begin.rb +45 -0
  85. data/lib/rubocop/cop/style/redundant_return.rb +59 -0
  86. data/lib/rubocop/cop/style/redundant_self.rb +83 -0
  87. data/lib/rubocop/cop/style/regexp_literal.rb +0 -2
  88. data/lib/rubocop/cop/style/rescue_modifier.rb +13 -21
  89. data/lib/rubocop/cop/style/semicolon.rb +15 -9
  90. data/lib/rubocop/cop/style/single_line_methods.rb +0 -4
  91. data/lib/rubocop/cop/style/space_after_comma_etc.rb +2 -2
  92. data/lib/rubocop/cop/style/space_after_control_keyword.rb +0 -1
  93. data/lib/rubocop/cop/style/string_literals.rb +5 -2
  94. data/lib/rubocop/cop/style/surrounding_space.rb +106 -91
  95. data/lib/rubocop/cop/style/tab.rb +4 -3
  96. data/lib/rubocop/cop/style/ternary_operator.rb +0 -4
  97. data/lib/rubocop/cop/style/trailing_whitespace.rb +4 -3
  98. data/lib/rubocop/cop/style/trivial_accessors.rb +51 -6
  99. data/lib/rubocop/cop/style/unless_else.rb +0 -2
  100. data/lib/rubocop/cop/style/variable_interpolation.rb +0 -2
  101. data/lib/rubocop/cop/style/when_then.rb +3 -3
  102. data/lib/rubocop/cop/style/while_until_do.rb +3 -5
  103. data/lib/rubocop/cop/style/word_array.rb +0 -2
  104. data/lib/rubocop/cop/util.rb +0 -4
  105. data/lib/rubocop/formatter/file_list_formatter.rb +18 -0
  106. data/lib/rubocop/formatter/formatter_set.rb +2 -1
  107. data/lib/rubocop/processed_source.rb +27 -0
  108. data/lib/rubocop/rake_task.rb +50 -0
  109. data/lib/rubocop/source_parser.rb +105 -0
  110. data/lib/rubocop/target_finder.rb +67 -0
  111. data/lib/rubocop/token.rb +22 -0
  112. data/lib/rubocop/version.rb +1 -1
  113. data/rubocop.gemspec +5 -3
  114. data/spec/project_spec.rb +0 -11
  115. data/spec/rubocop/cli_spec.rb +112 -6
  116. data/spec/rubocop/config_spec.rb +13 -17
  117. data/spec/rubocop/config_store_spec.rb +8 -23
  118. data/spec/rubocop/cops/commissioner_spec.rb +72 -0
  119. data/spec/rubocop/cops/corrector_spec.rb +63 -0
  120. data/spec/rubocop/cops/lint/assignment_in_condition_spec.rb +2 -2
  121. data/spec/rubocop/cops/lint/block_alignment_spec.rb +357 -0
  122. data/spec/rubocop/cops/lint/empty_ensure_spec.rb +33 -0
  123. data/spec/rubocop/cops/lint/end_alignment_spec.rb +0 -263
  124. data/spec/rubocop/cops/lint/ensure_return_spec.rb +6 -9
  125. data/spec/rubocop/cops/offence_spec.rb +28 -0
  126. data/spec/rubocop/cops/style/and_or_spec.rb +21 -11
  127. data/spec/rubocop/cops/style/ascii_identifiers_spec.rb +14 -0
  128. data/spec/rubocop/cops/style/avoid_global_vars_spec.rb +10 -14
  129. data/spec/rubocop/cops/style/character_literal_spec.rb +17 -2
  130. data/spec/rubocop/cops/style/colon_method_call_spec.rb +20 -15
  131. data/spec/rubocop/cops/style/comment_annotation_spec.rb +62 -0
  132. data/spec/rubocop/cops/style/encoding_spec.rb +7 -0
  133. data/spec/rubocop/cops/style/parentheses_around_condition_spec.rb +37 -9
  134. data/spec/rubocop/cops/style/redundant_begin_spec.rb +63 -0
  135. data/spec/rubocop/cops/style/redundant_return_spec.rb +64 -0
  136. data/spec/rubocop/cops/style/redundant_self_spec.rb +76 -0
  137. data/spec/rubocop/cops/style/string_literals_spec.rb +18 -13
  138. data/spec/rubocop/cops/style/trivial_accessors_spec.rb +110 -52
  139. data/spec/rubocop/cops/style/when_then_spec.rb +14 -7
  140. data/spec/rubocop/cops/style/while_until_do_spec.rb +12 -0
  141. data/spec/rubocop/cops/variable_inspector_spec.rb +3 -5
  142. data/spec/rubocop/formatter/file_list_formatter_spec.rb +33 -0
  143. data/spec/rubocop/processed_source_spec.rb +67 -0
  144. data/spec/rubocop/source_parser_spec.rb +141 -0
  145. data/spec/rubocop/target_finder_spec.rb +180 -0
  146. data/spec/rubocop/token_spec.rb +27 -0
  147. data/spec/spec_helper.rb +24 -4
  148. metadata +108 -18
  149. checksums.yaml +0 -7
@@ -25,11 +25,11 @@ module Rubocop
25
25
  hash = YAML.load_file(path)
26
26
 
27
27
  base_configs(path, hash['inherit_from']).reverse.each do |base_config|
28
+ if File.basename(base_config.loaded_path) == DOTFILE
29
+ make_excludes_absolute(base_config)
30
+ end
28
31
  base_config.each do |key, value|
29
32
  if value.is_a?(Hash)
30
- if key == 'AllCops' && value['Excludes']
31
- correct_relative_excludes(value, base_config, path)
32
- end
33
33
  hash[key] = hash.has_key?(key) ? merge(value, hash[key]) : value
34
34
  end
35
35
  end
@@ -41,14 +41,14 @@ module Rubocop
41
41
  config
42
42
  end
43
43
 
44
- def correct_relative_excludes(all_cops, base_config, path)
45
- all_cops['Excludes'].map! do |exclude_elem|
46
- if exclude_elem.is_a?(String) && exclude_elem =~ %r([^/].*/)
47
- rel_path = relative_path(base_config.loaded_path,
48
- File.dirname(path))
49
- rel_path.to_s.sub(/#{DOTFILE}$/, '') + exclude_elem
50
- else
51
- exclude_elem
44
+ def make_excludes_absolute(config)
45
+ if config['AllCops'] && config['AllCops']['Excludes']
46
+ config['AllCops']['Excludes'].map! do |exclude_elem|
47
+ if exclude_elem.is_a?(String) && !exclude_elem.start_with?('/')
48
+ File.join(File.dirname(config.loaded_path), exclude_elem)
49
+ else
50
+ exclude_elem
51
+ end
52
52
  end
53
53
  end
54
54
  end
@@ -58,15 +58,15 @@ module Rubocop
58
58
  path_name.relative_path_from(Pathname.new(base)).to_s
59
59
  end
60
60
 
61
- # Return a recursive merge of two hashes. That is, a normal hash
62
- # merge, with the addition that any value that is a hash, and
63
- # occurs in both arguments, will also be merged. And so on.
61
+ # Return an extended merge of two hashes. That is, a normal hash merge,
62
+ # with the addition that any value that is a hash, and occurs in both
63
+ # arguments (i.e., cop names), will also be merged.
64
64
  def merge(base_hash, derived_hash)
65
65
  result = {}
66
66
  base_hash.each do |key, value|
67
67
  result[key] = if derived_hash.has_key?(key)
68
68
  if value.is_a?(Hash)
69
- merge(value, derived_hash[key])
69
+ value.merge(derived_hash[key])
70
70
  else
71
71
  derived_hash[key]
72
72
  end
@@ -107,6 +107,7 @@ module Rubocop
107
107
  if found_files.any? && found_files.last != config_file
108
108
  add_excludes_from_higher_level(config, load_file(found_files.last))
109
109
  end
110
+ make_excludes_absolute(config)
110
111
  merge_with_default(config, config_file)
111
112
  end
112
113
 
@@ -116,9 +117,7 @@ module Rubocop
116
117
  config['AllCops']['Excludes'] ||= []
117
118
  highest_config['AllCops']['Excludes'].each do |path|
118
119
  unless path.is_a?(Regexp) || path.start_with?('/')
119
- diff_in_level = config.loaded_path.count('/') -
120
- highest_config.loaded_path.count('/')
121
- path = '../' * diff_in_level + path
120
+ path = File.join(File.dirname(highest_config.loaded_path), path)
122
121
  end
123
122
  config['AllCops']['Excludes'] << path
124
123
  end
@@ -209,10 +208,8 @@ module Rubocop
209
208
  end
210
209
 
211
210
  def file_to_exclude?(file)
212
- relative_file_path = relative_path_to_loaded_dir(file)
213
- patterns_to_exclude.any? do |pattern|
214
- match_path?(pattern, relative_file_path)
215
- end
211
+ file = File.join(Dir.pwd, file) unless file.start_with?('/')
212
+ patterns_to_exclude.any? { |pattern| match_path?(pattern, file) }
216
213
  end
217
214
 
218
215
  def patterns_to_include
@@ -3,10 +3,8 @@
3
3
  module Rubocop
4
4
  # Handles chaching of configurations and association of inspected
5
5
  # ruby files to configurations.
6
- module ConfigStore
7
- module_function
8
-
9
- def prepare
6
+ class ConfigStore
7
+ def initialize
10
8
  # @options_config stores a config that is specified in the command line.
11
9
  # This takes precedence over configs located in any directories
12
10
  @options_config = nil
@@ -0,0 +1,90 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ # Commissioner class is responsible for processing the AST and delagating
6
+ # work to the specified cops.
7
+ class Commissioner < Parser::AST::Processor
8
+ attr_reader :errors
9
+
10
+ METHODS_NOT_DEFINED_IN_PARSER_PROCESSOR = [
11
+ :on_sym, :on_str, :on_int, :on_float
12
+ ]
13
+
14
+ def self.callback_methods
15
+ Parser::AST::Processor.instance_methods.select do |method|
16
+ method.to_s =~ /^on_/
17
+ end + METHODS_NOT_DEFINED_IN_PARSER_PROCESSOR
18
+ end
19
+
20
+ # Methods that are not defined in Parser::AST::Processor
21
+ # won't have a `super` to call. So we should not attempt
22
+ # to invoke `super` when defining them.
23
+ def self.call_super(callback)
24
+ if METHODS_NOT_DEFINED_IN_PARSER_PROCESSOR.include?(callback)
25
+ ''
26
+ else
27
+ 'super'
28
+ end
29
+ end
30
+
31
+ def initialize(cops, options = {})
32
+ @cops = cops
33
+ @options = options
34
+ reset_errors
35
+ end
36
+
37
+ callback_methods.each do |callback|
38
+ class_eval <<-EOS
39
+ def #{callback}(node)
40
+ @cops.each do |cop|
41
+ if cop.respond_to?(:#{callback})
42
+ delegate_to(cop, :#{callback}, node)
43
+ end
44
+ end
45
+
46
+ #{call_super(callback)}
47
+ end
48
+ EOS
49
+ end
50
+
51
+ def investigate(processed_source)
52
+ reset_errors
53
+ invoke_cops_callback(processed_source)
54
+ process(processed_source.ast) if processed_source.ast
55
+ @cops.reduce([]) do |offences, cop|
56
+ offences.concat(cop.offences)
57
+ offences
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def reset_errors
64
+ @errors = Hash.new { |hash, k| hash[k] = [] }
65
+ end
66
+
67
+ # There are cops that require their own custom processing.
68
+ # If they define the #investigate method all input parameters passed
69
+ # to the commissioner will be passed to the cop too in order to do
70
+ # its own processing.
71
+ def invoke_cops_callback(processed_source)
72
+ @cops.each do |cop|
73
+ if cop.respond_to?(:investigate)
74
+ cop.investigate(processed_source)
75
+ end
76
+ end
77
+ end
78
+
79
+ def delegate_to(cop, callback, node)
80
+ cop.send callback, node
81
+ rescue => e
82
+ if @options[:raise_error]
83
+ fail e
84
+ else
85
+ @errors[cop] << e
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -2,31 +2,33 @@
2
2
 
3
3
  module Rubocop
4
4
  module Cop
5
- # A basic wrapper around Parser's tokens.
6
- class Token
7
- attr_reader :pos, :type, :text
8
-
9
- def initialize(pos, type, text)
10
- @pos, @type, @text = pos, type, text
11
- end
12
-
13
- def to_s
14
- "[[#{@pos.line}, #{@pos.column}], #{@type}, #{@text.inspect}]"
15
- end
16
- end
17
-
18
5
  # A scaffold for concrete cops.
19
6
  #
20
7
  # The Cop class is meant to be extended.
21
8
  #
22
9
  # Cops track offences and can autocorrect them of the fly.
23
- class Cop < Parser::Rewriter
10
+ #
11
+ # A commissioner object is responsible for traversing the AST and invoking
12
+ # the specific callbacks on each cop.
13
+ # If a cop needs to do its own processing of the AST or depends on
14
+ # something else it should define the #investigate method and do
15
+ # the processing there.
16
+ #
17
+ # @example
18
+ #
19
+ # class CustomCop < Cop
20
+ # def investigate(processed_source)
21
+ # # Do custom processing
22
+ # end
23
+ # end
24
+ class Cop
24
25
  extend AST::Sexp
25
26
 
26
27
  attr_accessor :offences
27
28
  attr_accessor :debug
28
29
  attr_accessor :autocorrect
29
30
  attr_writer :disabled_lines
31
+ attr_reader :corrections
30
32
 
31
33
  @all = []
32
34
  @config = {}
@@ -67,20 +69,8 @@ module Rubocop
67
69
  @offences = []
68
70
  @debug = false
69
71
  @autocorrect = false
70
- end
71
-
72
- def inspect(source_buffer, source, tokens, ast, comments)
73
- if autocorrect
74
- filename = source_buffer.instance_variable_get(:@name)
75
- new_source = rewrite(source_buffer, ast)
76
- unless new_source == source_buffer.source
77
- File.open(filename, 'w') { |f| f.write(new_source) }
78
- source_buffer.instance_variable_set(:@source, nil)
79
- source_buffer.read
80
- end
81
- else
82
- process(ast)
83
- end
72
+ @ignored_nodes = []
73
+ @corrections = []
84
74
  end
85
75
 
86
76
  def do_autocorrect(node)
@@ -90,9 +80,6 @@ module Rubocop
90
80
  def autocorrect_action(node)
91
81
  end
92
82
 
93
- def ignore_node(node)
94
- end
95
-
96
83
  def add_offence(severity, location, message)
97
84
  unless @disabled_lines && @disabled_lines.include?(location.line)
98
85
  message = debug ? "#{name}: #{message}" : message
@@ -104,8 +91,28 @@ module Rubocop
104
91
  self.class.cop_name
105
92
  end
106
93
 
94
+ def ignore_node(node)
95
+ @ignored_nodes << node
96
+ end
97
+
107
98
  private
108
99
 
100
+ def part_of_ignored_node?(node)
101
+ expression = node.loc.expression
102
+ @ignored_nodes.each do |ignored_node|
103
+ if ignored_node.loc.expression.begin_pos <= expression.begin_pos &&
104
+ ignored_node.loc.expression.end_pos >= expression.end_pos
105
+ return true
106
+ end
107
+ end
108
+
109
+ false
110
+ end
111
+
112
+ def ignored_node?(node)
113
+ @ignored_nodes.include?(node)
114
+ end
115
+
109
116
  def on_node(syms, sexp, excludes = [])
110
117
  yield sexp if Array(syms).include?(sexp.type)
111
118
 
@@ -0,0 +1,84 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ # This class takes a source buffer and rewrite its source
6
+ # based on the different correction rules supplied.
7
+ #
8
+ # Important!
9
+ # The nodes modified by the corrections should be part of the
10
+ # AST of the source_buffer.
11
+ class Corrector
12
+ #
13
+ # @param source_buffer [Parser::Source::Buffer]
14
+ # @param corrections [Array(#call)]
15
+ # Array of Objects that respond to #call. They will receive the
16
+ # corrector itself and should use its method to modify the source.
17
+ #
18
+ # @example
19
+ #
20
+ # class AndOrCorrector
21
+ # def initialize(node)
22
+ # @node = node
23
+ # end
24
+ #
25
+ # def call(corrector)
26
+ # replacement = (@node.type == :and ? '&&' : '||')
27
+ # corrector.replace(@node.loc.operator, replacement)
28
+ # end
29
+ # end
30
+ #
31
+ # corrections = [AndOrCorrector.new(node)]
32
+ # corrector = Corrector.new(source_buffer, corrections)
33
+ def initialize(source_buffer, corrections)
34
+ @source_buffer = source_buffer
35
+ @corrections = corrections
36
+ @source_rewriter = Parser::Source::Rewriter.new(source_buffer)
37
+ end
38
+
39
+ # Does the actual rewrite and returns string corresponding to
40
+ # the rewritten source.
41
+ #
42
+ # @return [String]
43
+ # TODO: Handle conflict exceptions raised from the Source::Rewriter
44
+ def rewrite
45
+ @corrections.each do |correction|
46
+ correction.call(self)
47
+ end
48
+
49
+ @source_rewriter.process
50
+ end
51
+
52
+ # Removes the source range.
53
+ #
54
+ # @param [Parser::Source::Range] range
55
+ def remove(range)
56
+ @source_rewriter.remove(range)
57
+ end
58
+
59
+ # Inserts new code before the given source range.
60
+ #
61
+ # @param [Parser::Source::Range] range
62
+ # @param [String] content
63
+ def insert_before(range, content)
64
+ @source_rewriter.insert_before(range, content)
65
+ end
66
+
67
+ # Inserts new code after the given source range.
68
+ #
69
+ # @param [Parser::Source::Range] range
70
+ # @param [String] content
71
+ def insert_after(range, content)
72
+ @source_rewriter.insert_after(range, content)
73
+ end
74
+
75
+ # Replaces the code of the source range `range` with `content`.
76
+ #
77
+ # @param [Parser::Source::Range] range
78
+ # @param [String] content
79
+ def replace(range, content)
80
+ @source_rewriter.replace(range, content)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -11,17 +11,14 @@ module Rubocop
11
11
 
12
12
  def on_if(node)
13
13
  check(node)
14
- super
15
14
  end
16
15
 
17
16
  def on_while(node)
18
17
  check(node)
19
- super
20
18
  end
21
19
 
22
20
  def on_until(node)
23
21
  check(node)
24
- super
25
22
  end
26
23
 
27
24
  private
@@ -0,0 +1,151 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks whether the end keywords are aligned properly for do
7
+ # end blocks.
8
+ #
9
+ # @example
10
+ #
11
+ # variable = lambda do |i|
12
+ # i
13
+ # end
14
+ class BlockAlignment < Cop
15
+ MSG = 'end at %d, %d is not aligned with %s at %d, %d'
16
+
17
+ def initialize
18
+ super
19
+ @inspected_blocks = []
20
+ end
21
+
22
+ def on_block(node)
23
+ return if already_processed_node?(node)
24
+ method, = *node
25
+ start_node = method.loc.expression.source =~ /\n/ ? method : node
26
+ check_block_alignment(start_node.loc.expression, node.loc)
27
+ end
28
+
29
+ def on_and(node)
30
+ return if already_processed_node?(node)
31
+
32
+ _left, right = *node
33
+ if right.type == :block
34
+ check_block_alignment(node.loc.expression, right.loc)
35
+ @inspected_blocks << right
36
+ end
37
+ end
38
+
39
+ alias_method :on_or, :on_and
40
+
41
+ def on_lvasgn(node)
42
+ _, children = *node
43
+ process_block_assignment(node, children)
44
+ end
45
+
46
+ alias_method :on_ivasgn, :on_lvasgn
47
+ alias_method :on_cvasgn, :on_lvasgn
48
+ alias_method :on_gvasgn, :on_lvasgn
49
+ alias_method :on_and_asgn, :on_lvasgn
50
+ alias_method :on_or_asgn, :on_lvasgn
51
+
52
+ def on_casgn(node)
53
+ _, _, children = *node
54
+ process_block_assignment(node, children)
55
+ end
56
+
57
+ def on_op_asgn(node)
58
+ variable, _op, args = *node
59
+ process_block_assignment(variable, args)
60
+ end
61
+
62
+ def on_send(node)
63
+ _receiver, _method, *args = *node
64
+ process_block_assignment(node, args.last)
65
+ end
66
+
67
+ def on_masgn(node)
68
+ variables, args = *node
69
+ process_block_assignment(variables, args)
70
+ end
71
+
72
+ private
73
+
74
+ def process_block_assignment(begin_node, other_node)
75
+ return unless other_node
76
+
77
+ block_node = find_block_node(other_node)
78
+ return unless block_node.type == :block
79
+
80
+ # If the block is an argument in a function call, align end with
81
+ # the block itself, and not with the function.
82
+ if begin_node.type == :send
83
+ _receiver, method, *_args = *begin_node
84
+ begin_node = block_node if method.to_s =~ /^\w+$/
85
+ end
86
+
87
+ # Align with the expression that is on the same line
88
+ # where the block is defined
89
+ if begin_node.type != :mlhs && block_is_on_next_line?(begin_node,
90
+ block_node)
91
+ return
92
+ end
93
+ return if already_processed_node?(block_node)
94
+
95
+ @inspected_blocks << block_node
96
+ check_block_alignment(begin_node.loc.expression, block_node.loc)
97
+ end
98
+
99
+ def find_block_node(node)
100
+ while [:send, :lvasgn].include?(node.type)
101
+ n = case node.type
102
+ when :send
103
+ find_block_or_send_node(node) || break
104
+ when :lvasgn
105
+ _variable, value = *node
106
+ value
107
+ end
108
+ node = n if n
109
+ end
110
+ node
111
+ end
112
+
113
+ def find_block_or_send_node(send_node)
114
+ receiver, _method, args = *send_node
115
+ [receiver, args].find do |subnode|
116
+ subnode && [:block, :send].include?(subnode.type)
117
+ end
118
+ end
119
+
120
+ def check_block_alignment(start_loc, block_loc)
121
+ match = start_loc.source.match(/\n(\s*)((end)?\.\S+)\Z/)
122
+ if match
123
+ start_line = start_loc.line + start_loc.source.count("\n")
124
+ start_column = match.captures[0].length
125
+ start_source = match.captures[1]
126
+ else
127
+ start_line = start_loc.line
128
+ start_column = start_loc.column
129
+ start_source = start_loc.source.lines.to_a.first.chomp
130
+ end
131
+ end_loc = block_loc.end
132
+ if block_loc.begin.line != end_loc.line &&
133
+ start_column != end_loc.column
134
+ add_offence(:warning,
135
+ end_loc,
136
+ sprintf(MSG, end_loc.line, end_loc.column,
137
+ start_source, start_line, start_column))
138
+ end
139
+ end
140
+
141
+ def already_processed_node?(node)
142
+ @inspected_blocks.include?(node)
143
+ end
144
+
145
+ def block_is_on_next_line?(begin_node, block_node)
146
+ begin_node.loc.line != block_node.loc.line
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end