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.
- data/.travis.yml +3 -1
- data/CHANGELOG.md +38 -0
- data/README.md +34 -0
- data/Rakefile +3 -0
- data/config/default.yml +14 -1
- data/config/enabled.yml +30 -7
- data/lib/rubocop.rb +15 -0
- data/lib/rubocop/cli.rb +48 -154
- data/lib/rubocop/config.rb +19 -22
- data/lib/rubocop/config_store.rb +2 -4
- data/lib/rubocop/cop/commissioner.rb +90 -0
- data/lib/rubocop/cop/cop.rb +38 -31
- data/lib/rubocop/cop/corrector.rb +84 -0
- data/lib/rubocop/cop/lint/assignment_in_condition.rb +0 -3
- data/lib/rubocop/cop/lint/block_alignment.rb +151 -0
- data/lib/rubocop/cop/lint/empty_ensure.rb +18 -0
- data/lib/rubocop/cop/lint/end_alignment.rb +0 -124
- data/lib/rubocop/cop/lint/end_in_method.rb +0 -2
- data/lib/rubocop/cop/lint/ensure_return.rb +3 -3
- data/lib/rubocop/cop/lint/eval.rb +0 -2
- data/lib/rubocop/cop/lint/handle_exceptions.rb +0 -2
- data/lib/rubocop/cop/lint/literal_in_condition.rb +0 -10
- data/lib/rubocop/cop/lint/loop.rb +0 -2
- data/lib/rubocop/cop/lint/rescue_exception.rb +0 -2
- data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +2 -2
- data/lib/rubocop/cop/lint/unreachable_code.rb +0 -2
- data/lib/rubocop/cop/lint/unused_local_variable.rb +2 -2
- data/lib/rubocop/cop/lint/void.rb +0 -2
- data/lib/rubocop/cop/offence.rb +9 -0
- data/lib/rubocop/cop/rails/validation.rb +2 -1
- data/lib/rubocop/cop/style/access_control.rb +4 -3
- data/lib/rubocop/cop/style/alias.rb +2 -4
- data/lib/rubocop/cop/style/align_parameters.rb +0 -2
- data/lib/rubocop/cop/style/and_or.rb +4 -6
- data/lib/rubocop/cop/style/ascii_comments.rb +2 -2
- data/lib/rubocop/cop/style/ascii_identifiers.rb +2 -2
- data/lib/rubocop/cop/style/attr.rb +0 -2
- data/lib/rubocop/cop/style/avoid_class_vars.rb +0 -1
- data/lib/rubocop/cop/style/avoid_for.rb +0 -2
- data/lib/rubocop/cop/style/avoid_global_vars.rb +3 -7
- data/lib/rubocop/cop/style/avoid_perl_backrefs.rb +0 -2
- data/lib/rubocop/cop/style/avoid_perlisms.rb +2 -4
- data/lib/rubocop/cop/style/begin_block.rb +0 -2
- data/lib/rubocop/cop/style/block_comments.rb +2 -2
- data/lib/rubocop/cop/style/block_nesting.rb +3 -3
- data/lib/rubocop/cop/style/blocks.rb +0 -2
- data/lib/rubocop/cop/style/case_equality.rb +0 -2
- data/lib/rubocop/cop/style/case_indentation.rb +0 -2
- data/lib/rubocop/cop/style/character_literal.rb +10 -6
- data/lib/rubocop/cop/style/class_and_module_camel_case.rb +0 -4
- data/lib/rubocop/cop/style/class_methods.rb +1 -1
- data/lib/rubocop/cop/style/collection_methods.rb +3 -5
- data/lib/rubocop/cop/style/colon_method_call.rb +3 -3
- data/lib/rubocop/cop/style/comment_annotation.rb +44 -0
- data/lib/rubocop/cop/style/constant_name.rb +0 -2
- data/lib/rubocop/cop/style/def_parentheses.rb +0 -8
- data/lib/rubocop/cop/style/documentation.rb +6 -2
- data/lib/rubocop/cop/style/dot_position.rb +0 -2
- data/lib/rubocop/cop/style/empty_line_between_defs.rb +0 -2
- data/lib/rubocop/cop/style/empty_lines.rb +10 -8
- data/lib/rubocop/cop/style/empty_literal.rb +3 -1
- data/lib/rubocop/cop/style/encoding.rb +7 -6
- data/lib/rubocop/cop/style/end_block.rb +0 -2
- data/lib/rubocop/cop/style/end_of_line.rb +4 -3
- data/lib/rubocop/cop/style/favor_join.rb +0 -2
- data/lib/rubocop/cop/style/favor_modifier.rb +9 -9
- data/lib/rubocop/cop/style/favor_sprintf.rb +0 -2
- data/lib/rubocop/cop/style/favor_unless_over_negated_if.rb +0 -2
- data/lib/rubocop/cop/style/hash_syntax.rb +0 -2
- data/lib/rubocop/cop/style/if_then_else.rb +0 -2
- data/lib/rubocop/cop/style/lambda.rb +0 -2
- data/lib/rubocop/cop/style/leading_comment_space.rb +2 -2
- data/lib/rubocop/cop/style/line_continuation.rb +4 -3
- data/lib/rubocop/cop/style/line_length.rb +4 -3
- data/lib/rubocop/cop/style/method_and_variable_snake_case.rb +4 -3
- data/lib/rubocop/cop/style/method_call_parentheses.rb +0 -2
- data/lib/rubocop/cop/style/method_length.rb +0 -4
- data/lib/rubocop/cop/style/not.rb +0 -2
- data/lib/rubocop/cop/style/op_method.rb +0 -2
- data/lib/rubocop/cop/style/parameter_lists.rb +0 -2
- data/lib/rubocop/cop/style/parentheses_around_condition.rb +12 -6
- data/lib/rubocop/cop/style/proc.rb +0 -2
- data/lib/rubocop/cop/style/reduce_arguments.rb +0 -2
- data/lib/rubocop/cop/style/redundant_begin.rb +45 -0
- data/lib/rubocop/cop/style/redundant_return.rb +59 -0
- data/lib/rubocop/cop/style/redundant_self.rb +83 -0
- data/lib/rubocop/cop/style/regexp_literal.rb +0 -2
- data/lib/rubocop/cop/style/rescue_modifier.rb +13 -21
- data/lib/rubocop/cop/style/semicolon.rb +15 -9
- data/lib/rubocop/cop/style/single_line_methods.rb +0 -4
- data/lib/rubocop/cop/style/space_after_comma_etc.rb +2 -2
- data/lib/rubocop/cop/style/space_after_control_keyword.rb +0 -1
- data/lib/rubocop/cop/style/string_literals.rb +5 -2
- data/lib/rubocop/cop/style/surrounding_space.rb +106 -91
- data/lib/rubocop/cop/style/tab.rb +4 -3
- data/lib/rubocop/cop/style/ternary_operator.rb +0 -4
- data/lib/rubocop/cop/style/trailing_whitespace.rb +4 -3
- data/lib/rubocop/cop/style/trivial_accessors.rb +51 -6
- data/lib/rubocop/cop/style/unless_else.rb +0 -2
- data/lib/rubocop/cop/style/variable_interpolation.rb +0 -2
- data/lib/rubocop/cop/style/when_then.rb +3 -3
- data/lib/rubocop/cop/style/while_until_do.rb +3 -5
- data/lib/rubocop/cop/style/word_array.rb +0 -2
- data/lib/rubocop/cop/util.rb +0 -4
- data/lib/rubocop/formatter/file_list_formatter.rb +18 -0
- data/lib/rubocop/formatter/formatter_set.rb +2 -1
- data/lib/rubocop/processed_source.rb +27 -0
- data/lib/rubocop/rake_task.rb +50 -0
- data/lib/rubocop/source_parser.rb +105 -0
- data/lib/rubocop/target_finder.rb +67 -0
- data/lib/rubocop/token.rb +22 -0
- data/lib/rubocop/version.rb +1 -1
- data/rubocop.gemspec +5 -3
- data/spec/project_spec.rb +0 -11
- data/spec/rubocop/cli_spec.rb +112 -6
- data/spec/rubocop/config_spec.rb +13 -17
- data/spec/rubocop/config_store_spec.rb +8 -23
- data/spec/rubocop/cops/commissioner_spec.rb +72 -0
- data/spec/rubocop/cops/corrector_spec.rb +63 -0
- data/spec/rubocop/cops/lint/assignment_in_condition_spec.rb +2 -2
- data/spec/rubocop/cops/lint/block_alignment_spec.rb +357 -0
- data/spec/rubocop/cops/lint/empty_ensure_spec.rb +33 -0
- data/spec/rubocop/cops/lint/end_alignment_spec.rb +0 -263
- data/spec/rubocop/cops/lint/ensure_return_spec.rb +6 -9
- data/spec/rubocop/cops/offence_spec.rb +28 -0
- data/spec/rubocop/cops/style/and_or_spec.rb +21 -11
- data/spec/rubocop/cops/style/ascii_identifiers_spec.rb +14 -0
- data/spec/rubocop/cops/style/avoid_global_vars_spec.rb +10 -14
- data/spec/rubocop/cops/style/character_literal_spec.rb +17 -2
- data/spec/rubocop/cops/style/colon_method_call_spec.rb +20 -15
- data/spec/rubocop/cops/style/comment_annotation_spec.rb +62 -0
- data/spec/rubocop/cops/style/encoding_spec.rb +7 -0
- data/spec/rubocop/cops/style/parentheses_around_condition_spec.rb +37 -9
- data/spec/rubocop/cops/style/redundant_begin_spec.rb +63 -0
- data/spec/rubocop/cops/style/redundant_return_spec.rb +64 -0
- data/spec/rubocop/cops/style/redundant_self_spec.rb +76 -0
- data/spec/rubocop/cops/style/string_literals_spec.rb +18 -13
- data/spec/rubocop/cops/style/trivial_accessors_spec.rb +110 -52
- data/spec/rubocop/cops/style/when_then_spec.rb +14 -7
- data/spec/rubocop/cops/style/while_until_do_spec.rb +12 -0
- data/spec/rubocop/cops/variable_inspector_spec.rb +3 -5
- data/spec/rubocop/formatter/file_list_formatter_spec.rb +33 -0
- data/spec/rubocop/processed_source_spec.rb +67 -0
- data/spec/rubocop/source_parser_spec.rb +141 -0
- data/spec/rubocop/target_finder_spec.rb +180 -0
- data/spec/rubocop/token_spec.rb +27 -0
- data/spec/spec_helper.rb +24 -4
- metadata +108 -18
- checksums.yaml +0 -7
data/lib/rubocop/config.rb
CHANGED
@@ -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
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
62
|
-
#
|
63
|
-
#
|
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(
|
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
|
-
|
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
|
-
|
213
|
-
patterns_to_exclude.any?
|
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
|
data/lib/rubocop/config_store.rb
CHANGED
@@ -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
|
-
|
7
|
-
|
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
|
data/lib/rubocop/cop/cop.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
@@ -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
|