rubocop 0.31.0 → 0.35.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 (177) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +315 -0
  3. data/README.md +199 -38
  4. data/config/default.yml +91 -12
  5. data/config/disabled.yml +45 -4
  6. data/config/enabled.yml +107 -9
  7. data/lib/rubocop/ast_node.rb +48 -0
  8. data/lib/rubocop/cli.rb +11 -1
  9. data/lib/rubocop/comment_config.rb +4 -1
  10. data/lib/rubocop/config.rb +26 -17
  11. data/lib/rubocop/config_loader.rb +61 -14
  12. data/lib/rubocop/cop/commissioner.rb +7 -12
  13. data/lib/rubocop/cop/cop.rb +43 -20
  14. data/lib/rubocop/cop/lint/block_alignment.rb +1 -1
  15. data/lib/rubocop/cop/lint/circular_argument_reference.rb +69 -0
  16. data/lib/rubocop/cop/lint/debugger.rb +9 -48
  17. data/lib/rubocop/cop/lint/def_end_alignment.rb +8 -4
  18. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +42 -23
  19. data/lib/rubocop/cop/lint/duplicate_methods.rb +2 -2
  20. data/lib/rubocop/cop/lint/duplicated_key.rb +37 -0
  21. data/lib/rubocop/cop/lint/end_alignment.rb +33 -13
  22. data/lib/rubocop/cop/lint/eval.rb +6 -2
  23. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +175 -0
  24. data/lib/rubocop/cop/lint/literal_in_condition.rb +0 -5
  25. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +10 -0
  26. data/lib/rubocop/cop/lint/nested_method_definition.rb +31 -0
  27. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +19 -1
  28. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +1 -1
  29. data/lib/rubocop/cop/lint/space_before_first_arg.rb +1 -1
  30. data/lib/rubocop/cop/lint/unneeded_disable.rb +72 -0
  31. data/lib/rubocop/cop/lint/unused_block_argument.rb +6 -0
  32. data/lib/rubocop/cop/lint/unused_method_argument.rb +8 -0
  33. data/lib/rubocop/cop/metrics/abc_size.rb +17 -6
  34. data/lib/rubocop/cop/metrics/class_length.rb +1 -1
  35. data/lib/rubocop/cop/metrics/method_length.rb +1 -3
  36. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  37. data/lib/rubocop/cop/metrics/parameter_lists.rb +1 -1
  38. data/lib/rubocop/cop/mixin/access_modifier_node.rb +1 -1
  39. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -2
  40. data/lib/rubocop/cop/mixin/autocorrect_alignment.rb +28 -4
  41. data/lib/rubocop/cop/mixin/autocorrect_unless_changing_ast.rb +26 -3
  42. data/lib/rubocop/cop/mixin/check_assignment.rb +2 -3
  43. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +59 -12
  44. data/lib/rubocop/cop/mixin/configurable_max.rb +1 -1
  45. data/lib/rubocop/cop/mixin/configurable_naming.rb +14 -3
  46. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +1 -3
  47. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +10 -1
  48. data/lib/rubocop/cop/mixin/first_element_line_break.rb +41 -0
  49. data/lib/rubocop/cop/mixin/if_node.rb +10 -0
  50. data/lib/rubocop/cop/mixin/method_preference.rb +28 -0
  51. data/lib/rubocop/cop/mixin/negative_conditional.rb +1 -1
  52. data/lib/rubocop/cop/mixin/on_method_def.rb +4 -5
  53. data/lib/rubocop/cop/mixin/safe_assignment.rb +3 -14
  54. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +8 -1
  55. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +8 -1
  56. data/lib/rubocop/cop/mixin/statement_modifier.rb +4 -7
  57. data/lib/rubocop/cop/mixin/string_help.rb +1 -1
  58. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -1
  59. data/lib/rubocop/cop/mixin/surrounding_space.rb +5 -4
  60. data/lib/rubocop/cop/offense.rb +16 -3
  61. data/lib/rubocop/cop/performance/case_when_splat.rb +160 -0
  62. data/lib/rubocop/cop/performance/count.rb +35 -30
  63. data/lib/rubocop/cop/performance/detect.rb +16 -3
  64. data/lib/rubocop/cop/performance/fixed_size.rb +50 -0
  65. data/lib/rubocop/cop/performance/flat_map.rb +3 -3
  66. data/lib/rubocop/cop/performance/sample.rb +103 -59
  67. data/lib/rubocop/cop/performance/size.rb +2 -1
  68. data/lib/rubocop/cop/performance/string_replacement.rb +187 -0
  69. data/lib/rubocop/cop/rails/action_filter.rb +31 -5
  70. data/lib/rubocop/cop/rails/date.rb +15 -14
  71. data/lib/rubocop/cop/rails/pluralization_grammar.rb +97 -0
  72. data/lib/rubocop/cop/rails/read_write_attribute.rb +1 -1
  73. data/lib/rubocop/cop/rails/time_zone.rb +46 -18
  74. data/lib/rubocop/cop/style/alias.rb +1 -0
  75. data/lib/rubocop/cop/style/align_hash.rb +8 -15
  76. data/lib/rubocop/cop/style/align_parameters.rb +19 -7
  77. data/lib/rubocop/cop/style/and_or.rb +42 -13
  78. data/lib/rubocop/cop/style/auto_resource_cleanup.rb +2 -1
  79. data/lib/rubocop/cop/style/block_comments.rb +4 -2
  80. data/lib/rubocop/cop/style/block_delimiters.rb +69 -24
  81. data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +40 -12
  82. data/lib/rubocop/cop/style/case_indentation.rb +18 -4
  83. data/lib/rubocop/cop/style/collection_methods.rb +2 -20
  84. data/lib/rubocop/cop/style/command_literal.rb +2 -10
  85. data/lib/rubocop/cop/style/comment_annotation.rb +29 -8
  86. data/lib/rubocop/cop/style/copyright.rb +5 -3
  87. data/lib/rubocop/cop/style/documentation.rb +21 -12
  88. data/lib/rubocop/cop/style/dot_position.rb +6 -0
  89. data/lib/rubocop/cop/style/double_negation.rb +4 -15
  90. data/lib/rubocop/cop/style/each_with_object.rb +17 -4
  91. data/lib/rubocop/cop/style/else_alignment.rb +2 -1
  92. data/lib/rubocop/cop/style/empty_else.rb +25 -0
  93. data/lib/rubocop/cop/style/empty_line_between_defs.rb +39 -14
  94. data/lib/rubocop/cop/style/encoding.rb +10 -4
  95. data/lib/rubocop/cop/style/extra_spacing.rb +126 -5
  96. data/lib/rubocop/cop/style/first_array_element_line_break.rb +41 -0
  97. data/lib/rubocop/cop/style/first_hash_element_line_break.rb +35 -0
  98. data/lib/rubocop/cop/style/first_method_argument_line_break.rb +37 -0
  99. data/lib/rubocop/cop/style/first_method_parameter_line_break.rb +42 -0
  100. data/lib/rubocop/cop/style/first_parameter_indentation.rb +5 -3
  101. data/lib/rubocop/cop/style/for.rb +2 -1
  102. data/lib/rubocop/cop/style/hash_syntax.rb +5 -0
  103. data/lib/rubocop/cop/style/if_unless_modifier.rb +32 -5
  104. data/lib/rubocop/cop/style/indent_hash.rb +67 -37
  105. data/lib/rubocop/cop/style/indentation_width.rb +36 -10
  106. data/lib/rubocop/cop/style/initial_indentation.rb +37 -0
  107. data/lib/rubocop/cop/style/leading_comment_space.rb +3 -2
  108. data/lib/rubocop/cop/style/method_call_parentheses.rb +28 -1
  109. data/lib/rubocop/cop/style/method_def_parentheses.rb +10 -7
  110. data/lib/rubocop/cop/style/multiline_operation_indentation.rb +21 -24
  111. data/lib/rubocop/cop/style/mutable_constant.rb +35 -0
  112. data/lib/rubocop/cop/style/nested_modifier.rb +97 -0
  113. data/lib/rubocop/cop/style/next.rb +50 -15
  114. data/lib/rubocop/cop/style/non_nil_check.rb +12 -8
  115. data/lib/rubocop/cop/style/one_line_conditional.rb +8 -4
  116. data/lib/rubocop/cop/style/option_hash.rb +64 -0
  117. data/lib/rubocop/cop/style/optional_arguments.rb +49 -0
  118. data/lib/rubocop/cop/style/parallel_assignment.rb +218 -0
  119. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +3 -66
  120. data/lib/rubocop/cop/style/predicate_name.rb +7 -2
  121. data/lib/rubocop/cop/style/redundant_begin.rb +2 -13
  122. data/lib/rubocop/cop/style/redundant_freeze.rb +37 -0
  123. data/lib/rubocop/cop/style/redundant_return.rb +32 -3
  124. data/lib/rubocop/cop/style/regexp_literal.rb +2 -10
  125. data/lib/rubocop/cop/style/rescue_ensure_alignment.rb +81 -0
  126. data/lib/rubocop/cop/style/rescue_modifier.rb +30 -22
  127. data/lib/rubocop/cop/style/send.rb +18 -0
  128. data/lib/rubocop/cop/style/signal_exception.rb +24 -11
  129. data/lib/rubocop/cop/style/single_line_methods.rb +8 -9
  130. data/lib/rubocop/cop/style/single_space_before_first_arg.rb +1 -1
  131. data/lib/rubocop/cop/style/space_around_operators.rb +2 -0
  132. data/lib/rubocop/cop/style/space_inside_string_interpolation.rb +61 -0
  133. data/lib/rubocop/cop/style/special_global_vars.rb +4 -2
  134. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +108 -0
  135. data/lib/rubocop/cop/style/string_methods.rb +32 -0
  136. data/lib/rubocop/cop/style/struct_inheritance.rb +11 -10
  137. data/lib/rubocop/cop/style/symbol_literal.rb +1 -1
  138. data/lib/rubocop/cop/style/symbol_proc.rb +62 -13
  139. data/lib/rubocop/cop/style/trailing_blank_lines.rb +9 -1
  140. data/lib/rubocop/cop/style/trailing_comma.rb +17 -7
  141. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -2
  142. data/lib/rubocop/cop/style/trivial_accessors.rb +10 -1
  143. data/lib/rubocop/cop/style/unneeded_percent_q.rb +31 -20
  144. data/lib/rubocop/cop/style/variable_name.rb +5 -0
  145. data/lib/rubocop/cop/style/while_until_do.rb +1 -1
  146. data/lib/rubocop/cop/style/word_array.rb +15 -2
  147. data/lib/rubocop/cop/team.rb +25 -5
  148. data/lib/rubocop/cop/util.rb +7 -2
  149. data/lib/rubocop/cop/variable_force/locatable.rb +6 -6
  150. data/lib/rubocop/cop/variable_force.rb +10 -10
  151. data/lib/rubocop/formatter/base_formatter.rb +1 -1
  152. data/lib/rubocop/formatter/disabled_config_formatter.rb +70 -8
  153. data/lib/rubocop/formatter/formatter_set.rb +27 -1
  154. data/lib/rubocop/formatter/progress_formatter.rb +10 -2
  155. data/lib/rubocop/formatter/simple_text_formatter.rb +1 -1
  156. data/lib/rubocop/node_pattern.rb +390 -0
  157. data/lib/rubocop/options.rb +148 -81
  158. data/lib/rubocop/processed_source.rb +7 -2
  159. data/lib/rubocop/rake_task.rb +1 -1
  160. data/lib/rubocop/remote_config.rb +60 -0
  161. data/lib/rubocop/result_cache.rb +123 -0
  162. data/lib/rubocop/runner.rb +85 -22
  163. data/lib/rubocop/target_finder.rb +4 -4
  164. data/lib/rubocop/token.rb +2 -1
  165. data/lib/rubocop/version.rb +1 -1
  166. data/lib/rubocop/warning.rb +11 -0
  167. data/lib/rubocop.rb +32 -3
  168. data/relnotes/v0.32.0.md +139 -0
  169. data/relnotes/v0.32.1.md +122 -0
  170. data/relnotes/v0.33.0.md +157 -0
  171. data/relnotes/v0.34.0.md +182 -0
  172. data/relnotes/v0.34.1.md +129 -0
  173. data/relnotes/v0.34.2.md +139 -0
  174. data/relnotes/v0.35.0.md +210 -0
  175. data/rubocop.gemspec +4 -4
  176. metadata +50 -12
  177. data/lib/rubocop/cop/performance/parallel_assignment.rb +0 -79
@@ -3,86 +3,30 @@
3
3
  require 'optparse'
4
4
 
5
5
  module RuboCop
6
- # This module contains help texts for command line options.
7
- module OptionsHelp
8
- TEXT = {
9
- only: 'Run only the given cop(s).',
10
- only_guide_cops: ['Run only cops for rules that link to a',
11
- 'style guide.'],
12
- except: 'Disable the given cop(s).',
13
- require: 'Require Ruby file.',
14
- config: 'Specify configuration file.',
15
- auto_gen_config: ['Generate a configuration file acting as a',
16
- 'TODO list.'],
17
- force_exclusion: ['Force excluding files specified in the',
18
- 'configuration `Exclude` even if they are',
19
- 'explicitly passed as arguments.'],
20
- format: ['Choose an output formatter. This option',
21
- 'can be specified multiple times to enable',
22
- 'multiple formatters at the same time.',
23
- ' [p]rogress (default)',
24
- ' [s]imple',
25
- ' [c]lang',
26
- ' [d]isabled cops via inline comments',
27
- ' [fu]ubar',
28
- ' [e]macs',
29
- ' [j]son',
30
- ' [h]tml',
31
- ' [fi]les',
32
- ' [o]ffenses',
33
- ' custom formatter class name'],
34
- out: ['Write output to a file instead of STDOUT.',
35
- 'This option applies to the previously',
36
- 'specified --format, or the default format',
37
- 'if no format is specified.'],
38
- fail_level: ['Minimum severity (A/R/C/W/E/F) for exit',
39
- 'with error code.'],
40
- show_cops: ['Shows the given cops, or all cops by',
41
- 'default, and their configurations for the',
42
- 'current directory.'],
43
- fail_fast: ['Inspect files in order of modification',
44
- 'time and stop after the first file',
45
- 'containing offenses.'],
46
- debug: 'Display debug info.',
47
- display_cop_names: 'Display cop names in offense messages.',
48
- display_style_guide: 'Display style guide URLs in offense messages.',
49
- rails: 'Run extra Rails cops.',
50
- lint: 'Run only lint cops.',
51
- auto_correct: 'Auto-correct offenses.',
52
- no_color: 'Disable color output.',
53
- version: 'Display version.',
54
- verbose_version: 'Display verbose version.'
55
- }
56
- end
57
-
58
6
  # This class handles command line options.
59
7
  class Options
60
8
  DEFAULT_FORMATTER = 'progress'
61
9
  EXITING_OPTIONS = [:version, :verbose_version, :show_cops]
10
+ DEFAULT_MAXIMUM_EXCLUSION_ITEMS = 15
62
11
 
63
12
  def initialize
64
13
  @options = {}
14
+ @validator = OptionsValidator.new(@options)
65
15
  end
66
16
 
67
17
  def parse(args)
68
18
  define_options(args).parse!(args)
19
+ # The --no-color CLI option sets `color: false` so we don't want the
20
+ # `no_color` key, which is created automatically.
21
+ @options.delete(:no_color)
69
22
 
70
- validate_compatibility
71
-
72
- [@options, args]
73
- end
74
-
75
- # Cop name validation must be done later than option parsing, so it's not
76
- # called from within this class.
77
- def self.validate_cop_list(names)
78
- return unless names
23
+ @validator.validate_compatibility
79
24
 
80
- namespaces = Cop::Cop.all.types.map { |t| t.to_s.capitalize }
81
- names.each do |name|
82
- next if Cop::Cop.all.any? { |c| c.cop_name == name } ||
83
- namespaces.include?(name)
84
- fail ArgumentError, "Unrecognized cop or namespace: #{name}."
25
+ if @options[:stdin] && !args.one?
26
+ fail ArgumentError, '-s/--stdin requires exactly one path.'
85
27
  end
28
+
29
+ [@options, args]
86
30
  end
87
31
 
88
32
  private
@@ -91,6 +35,7 @@ module RuboCop
91
35
  OptionParser.new do |opts|
92
36
  opts.banner = 'Usage: rubocop [options] [file1, file2, ...]'
93
37
 
38
+ add_list_options(opts)
94
39
  add_only_options(opts)
95
40
  add_configuration_options(opts, args)
96
41
  add_formatting_options(opts)
@@ -103,11 +48,6 @@ module RuboCop
103
48
  end
104
49
  end
105
50
 
106
- def validate_compatibility
107
- return unless (incompat = @options.keys & EXITING_OPTIONS).size > 1
108
- fail ArgumentError, "Incompatible cli options: #{incompat.inspect}"
109
- end
110
-
111
51
  def add_only_options(opts)
112
52
  add_cop_selection_csv_option('except', opts)
113
53
  add_cop_selection_csv_option('only', opts)
@@ -116,9 +56,14 @@ module RuboCop
116
56
 
117
57
  def add_cop_selection_csv_option(option, opts)
118
58
  option(opts, "--#{option} [COP1,COP2,...]") do |list|
119
- @options[:"#{option}"] = list.split(',').map do |c|
120
- Cop::Cop.qualified_cop_name(c, "--#{option} option")
121
- end
59
+ @options[:"#{option}"] =
60
+ if list.empty?
61
+ ['']
62
+ else
63
+ list.split(',').map do |c|
64
+ Cop::Cop.qualified_cop_name(c, "--#{option} option")
65
+ end
66
+ end
122
67
  end
123
68
  end
124
69
 
@@ -126,12 +71,15 @@ module RuboCop
126
71
  option(opts, '-c', '--config FILE')
127
72
 
128
73
  option(opts, '--auto-gen-config') do
129
- validate_auto_gen_config_option(args)
130
74
  @options[:formatters] = [[DEFAULT_FORMATTER],
131
75
  [Formatter::DisabledConfigFormatter,
132
76
  ConfigLoader::AUTO_GENERATED_FILE]]
133
77
  end
134
78
 
79
+ option(opts, '--exclude-limit COUNT') do
80
+ @validator.validate_exclude_limit_option(args)
81
+ end
82
+
135
83
  option(opts, '--force-exclusion')
136
84
  end
137
85
 
@@ -164,11 +112,16 @@ module RuboCop
164
112
 
165
113
  def add_boolean_flags(opts)
166
114
  option(opts, '-F', '--fail-fast')
115
+ option(opts, '-C', '--cache FLAG')
167
116
  option(opts, '-d', '--debug')
168
117
  option(opts, '-D', '--display-cop-names')
118
+ option(opts, '-E', '--extra-details')
169
119
  option(opts, '-S', '--display-style-guide')
170
120
  option(opts, '-R', '--rails')
171
- option(opts, '-l', '--lint')
121
+ option(opts, '-l', '--lint') do
122
+ @options[:only] ||= []
123
+ @options[:only] << 'Lint'
124
+ end
172
125
  option(opts, '-a', '--auto-correct')
173
126
 
174
127
  @options[:color] = true
@@ -176,6 +129,11 @@ module RuboCop
176
129
 
177
130
  option(opts, '-v', '--version')
178
131
  option(opts, '-V', '--verbose-version')
132
+ option(opts, '-s', '--stdin') { @options[:stdin] = $stdin.read }
133
+ end
134
+
135
+ def add_list_options(opts)
136
+ option(opts, '-L', '--list-target-files')
179
137
  end
180
138
 
181
139
  # Sets a value in the @options hash, based on the given long option and its
@@ -193,14 +151,123 @@ module RuboCop
193
151
  # e.g. [..., '--auto-correct', ...] to :auto_correct.
194
152
  def long_opt_symbol(args)
195
153
  long_opt = args.find { |arg| arg.start_with?('--') }
196
- long_opt[2..-1].sub(/ .*/, '').gsub(/-/, '_').to_sym
154
+ long_opt[2..-1].sub(/ .*/, '').tr('-', '_').to_sym
155
+ end
156
+ end
157
+
158
+ # Validates option arguments and the options' compatibilty with each other.
159
+ class OptionsValidator
160
+ def initialize(options)
161
+ @options = options
197
162
  end
198
163
 
199
- def validate_auto_gen_config_option(args)
200
- return unless args.any?
164
+ # Cop name validation must be done later than option parsing, so it's not
165
+ # called from within Options.
166
+ def self.validate_cop_list(names)
167
+ return unless names
168
+
169
+ namespaces = Cop::Cop.all.types.map { |t| t.to_s.capitalize }
170
+ names.each do |name|
171
+ next if Cop::Cop.all.any? { |c| c.cop_name == name }
172
+ next if namespaces.include?(name)
173
+ next if %w(Syntax Lint/Syntax).include?(name)
201
174
 
202
- warn '--auto-gen-config can not be combined with any other arguments.'
203
- exit(1)
175
+ fail ArgumentError, "Unrecognized cop or namespace: #{name}."
176
+ end
204
177
  end
178
+
179
+ def validate_compatibility
180
+ if @options.key?(:only) &&
181
+ (@options[:only] & %w(Lint/UnneededDisable UnneededDisable)).any?
182
+ fail ArgumentError, 'Lint/UnneededDisable can not be used with --only.'
183
+ end
184
+ if @options.key?(:except) &&
185
+ (@options[:except] & %w(Lint/Syntax Syntax)).any?
186
+ fail ArgumentError, 'Syntax checking can not be turned off.'
187
+ end
188
+ if @options.key?(:cache) && !%w(true false).include?(@options[:cache])
189
+ fail ArgumentError, '-C/--cache argument must be true or false'
190
+ end
191
+ return if (incompat = @options.keys & Options::EXITING_OPTIONS).size <= 1
192
+ fail ArgumentError, "Incompatible cli options: #{incompat.inspect}"
193
+ end
194
+
195
+ def validate_exclude_limit_option(args)
196
+ if @options[:exclude_limit] !~ /^\d+$/
197
+ # Emulate OptionParser's behavior to make failures consistent regardless
198
+ # of option order.
199
+ fail OptionParser::MissingArgument
200
+ end
201
+
202
+ # --exclude-limit is valid if there's a parsed or yet unparsed
203
+ # --auto-gen-config.
204
+ return if @options[:auto_gen_config] || args.include?('--auto-gen-config')
205
+
206
+ fail ArgumentError,
207
+ '--exclude-limit can only be used with --auto-gen-config.'
208
+ end
209
+ end
210
+
211
+ # This module contains help texts for command line options.
212
+ module OptionsHelp
213
+ MAX_EXCL = RuboCop::Options::DEFAULT_MAXIMUM_EXCLUSION_ITEMS.to_s
214
+ TEXT = {
215
+ only: 'Run only the given cop(s).',
216
+ only_guide_cops: ['Run only cops for rules that link to a',
217
+ 'style guide.'],
218
+ except: 'Disable the given cop(s).',
219
+ require: 'Require Ruby file.',
220
+ config: 'Specify configuration file.',
221
+ auto_gen_config: ['Generate a configuration file acting as a',
222
+ 'TODO list.'],
223
+ exclude_limit: ['Used together with --auto-gen-config to',
224
+ 'set the limit for how many Exclude',
225
+ "properties to generate. Default is #{MAX_EXCL}."],
226
+ force_exclusion: ['Force excluding files specified in the',
227
+ 'configuration `Exclude` even if they are',
228
+ 'explicitly passed as arguments.'],
229
+ format: ['Choose an output formatter. This option',
230
+ 'can be specified multiple times to enable',
231
+ 'multiple formatters at the same time.',
232
+ ' [p]rogress (default)',
233
+ ' [s]imple',
234
+ ' [c]lang',
235
+ ' [d]isabled cops via inline comments',
236
+ ' [fu]ubar',
237
+ ' [e]macs',
238
+ ' [j]son',
239
+ ' [h]tml',
240
+ ' [fi]les',
241
+ ' [o]ffenses',
242
+ ' custom formatter class name'],
243
+ out: ['Write output to a file instead of STDOUT.',
244
+ 'This option applies to the previously',
245
+ 'specified --format, or the default format',
246
+ 'if no format is specified.'],
247
+ fail_level: ['Minimum severity (A/R/C/W/E/F) for exit',
248
+ 'with error code.'],
249
+ show_cops: ['Shows the given cops, or all cops by',
250
+ 'default, and their configurations for the',
251
+ 'current directory.'],
252
+ fail_fast: ['Inspect files in order of modification',
253
+ 'time and stop after the first file',
254
+ 'containing offenses.'],
255
+ cache: ["Use result caching (FLAG=true) or don't",
256
+ '(FLAG=false), default determined by',
257
+ 'configuration parameter AllCops: UseCache.'],
258
+ debug: 'Display debug info.',
259
+ display_cop_names: 'Display cop names in offense messages.',
260
+ display_style_guide: 'Display style guide URLs in offense messages.',
261
+ extra_details: 'Display extra details in offense messages.',
262
+ rails: 'Run extra Rails cops.',
263
+ lint: 'Run only lint cops.',
264
+ list_target_files: 'List all files RuboCop will inspect.',
265
+ auto_correct: 'Auto-correct offenses.',
266
+ no_color: 'Disable color output.',
267
+ version: 'Display version.',
268
+ verbose_version: 'Display verbose version.',
269
+ stdin: ['Pipe source from STDIN.',
270
+ 'This is useful for editor integration.']
271
+ }
205
272
  end
206
273
  end
@@ -13,7 +13,12 @@ module RuboCop
13
13
  :parser_error, :raw_source
14
14
 
15
15
  def self.from_file(path)
16
- new(File.read(path), path)
16
+ file = File.read(path)
17
+ new(file, path)
18
+ rescue Errno::ENOENT
19
+ abort("#{Rainbow('rubocop: No such file or directory').red} -- #{path}")
20
+ rescue => ex
21
+ abort("#{Rainbow("rubocop: #{ex.message}").red} -- #{path}")
17
22
  end
18
23
 
19
24
  def initialize(source, path = nil)
@@ -41,7 +46,7 @@ module RuboCop
41
46
  def lines
42
47
  @lines ||= begin
43
48
  all_lines = raw_source.lines.map(&:chomp)
44
- last_token_line = tokens.any? ? tokens.last.pos.line : all_lines.count
49
+ last_token_line = tokens.any? ? tokens.last.pos.line : all_lines.size
45
50
  result = []
46
51
  all_lines.each_with_index do |line, ix|
47
52
  break if ix >= last_token_line && line == '__END__'
@@ -48,7 +48,7 @@ module RuboCop
48
48
  cli = CLI.new
49
49
  puts 'Running RuboCop...' if verbose
50
50
  result = cli.run(options)
51
- abort('RuboCop failed!') if fail_on_error unless result == 0
51
+ abort('RuboCop failed!') if result != 0 && fail_on_error
52
52
  end
53
53
 
54
54
  def full_options
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ require 'net/http'
4
+
5
+ module RuboCop
6
+ # Common methods and behaviors for dealing with remote config files.
7
+ class RemoteConfig
8
+ CACHE_LIFETIME = 24 * 60 * 60
9
+
10
+ def initialize(url)
11
+ @uri = URI.parse(url)
12
+ end
13
+
14
+ def file
15
+ return cache_path unless cache_path_expired?
16
+
17
+ http = Net::HTTP.new(@uri.hostname, @uri.port)
18
+ http.use_ssl = true if @uri.instance_of? URI::HTTPS
19
+
20
+ request = Net::HTTP::Get.new(@uri.request_uri)
21
+ if cache_path_exists?
22
+ request['If-Modified-Since'] = File.stat(cache_path).mtime.rfc2822
23
+ end
24
+ response = http.request(request)
25
+
26
+ cache_path.tap do |f|
27
+ if response.is_a?(Net::HTTPSuccess)
28
+ open f, 'w' do |io|
29
+ io.write response.body
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def cache_path
38
+ ".rubocop-#{cache_name_from_uri}"
39
+ end
40
+
41
+ def cache_path_exists?
42
+ @cache_path_exists ||= File.exist?(cache_path)
43
+ end
44
+
45
+ def cache_path_expired?
46
+ return true unless cache_path_exists?
47
+
48
+ @cache_path_expired ||= begin
49
+ file_age = (Time.now - File.stat(cache_path).mtime).to_f
50
+ (file_age / CACHE_LIFETIME) > 1
51
+ end
52
+ end
53
+
54
+ def cache_name_from_uri
55
+ uri = @uri.clone
56
+ uri.query = nil
57
+ uri.to_s.gsub!(/[^0-9A-Za-z]/, '-')
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,123 @@
1
+ # encoding: utf-8
2
+
3
+ require 'digest/md5'
4
+ require 'find'
5
+ require 'tmpdir'
6
+ require 'etc'
7
+
8
+ module RuboCop
9
+ # Provides functionality for caching rubocop runs.
10
+ class ResultCache
11
+ # Include the user name in the path as a simple means of avoiding write
12
+ # collisions.
13
+ def initialize(file, options, config_store, cache_root = nil)
14
+ cache_root ||= ResultCache.cache_root(config_store)
15
+ @path = File.join(cache_root, rubocop_checksum, RUBY_VERSION,
16
+ relevant_options(options),
17
+ file_checksum(file, config_store))
18
+ end
19
+
20
+ def valid?
21
+ File.exist?(@path)
22
+ end
23
+
24
+ def load
25
+ Marshal.load(IO.binread(@path))
26
+ end
27
+
28
+ def save(offenses, disabled_line_ranges, comments)
29
+ FileUtils.mkdir_p(File.dirname(@path))
30
+ preliminary_path = "#{@path}_#{rand(1_000_000_000)}"
31
+ File.open(preliminary_path, 'wb') do |f|
32
+ # The Hash[x.sort] call is a trick that converts a Hash with a default
33
+ # block to a Hash without a default block. Thus making it possible to
34
+ # dump.
35
+ f.write(Marshal.dump([offenses, Hash[disabled_line_ranges.sort],
36
+ comments]))
37
+ end
38
+ # The preliminary path is used so that if there are multiple RuboCop
39
+ # processes trying to save data for the same inspected file
40
+ # simultaneously, the only problem we run in to is a competition who gets
41
+ # to write to the final file. The contents are the same, so no corruption
42
+ # of data should occur.
43
+ FileUtils.mv(preliminary_path, @path)
44
+ end
45
+
46
+ # Remove old files so that the cache doesn't grow too big. When the
47
+ # threshold MaxFilesInCache has been exceeded, the oldest 50% all the files
48
+ # in the cache are removed. The reason for removing so much is that
49
+ # cleaning should be done relatively seldom, since there is a slight risk
50
+ # that some other RuboCop process was just about to read the file, when
51
+ # there's parallel execution and the cache is shared.
52
+ def self.cleanup(config_store, verbose, cache_root = nil)
53
+ return if inhibit_cleanup # OPTIMIZE: For faster testing
54
+ cache_root ||= cache_root(config_store)
55
+ return unless File.exist?(cache_root)
56
+
57
+ files, dirs = Find.find(cache_root).partition { |path| File.file?(path) }
58
+ if files.length > config_store.for('.')['AllCops']['MaxFilesInCache'] &&
59
+ files.length > 1
60
+ # Add 1 to half the number of files, so that we remove the file if
61
+ # there's only 1 left.
62
+ remove_count = 1 + files.length / 2
63
+ if verbose
64
+ puts "Removing the #{remove_count} oldest files from #{cache_root}"
65
+ end
66
+ sorted = files.sort_by { |path| File.mtime(path) }
67
+ begin
68
+ sorted[0, remove_count].each { |path| File.delete(path) }
69
+ dirs.each { |dir| Dir.rmdir(dir) if Dir["#{dir}/*"].empty? }
70
+ rescue Errno::ENOENT
71
+ # This can happen if parallel RuboCop invocations try to remove the
72
+ # same files. No problem.
73
+ puts $ERROR_INFO if verbose
74
+ end
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def self.cache_root(config_store)
81
+ root = config_store.for('.')['AllCops']['CacheRootDirectory']
82
+ root = File.join(Dir.tmpdir, Etc.getlogin) if root == '/tmp'
83
+ File.join(root, 'rubocop_cache')
84
+ end
85
+
86
+ def file_checksum(file, config_store)
87
+ Digest::MD5.hexdigest(Dir.pwd + file + IO.read(file) +
88
+ config_store.for(file).to_s)
89
+ rescue Errno::ENOENT
90
+ # Spurious files that come and go should not cause a crash, at least not
91
+ # here.
92
+ '_'
93
+ end
94
+
95
+ class << self
96
+ attr_accessor :source_checksum, :inhibit_cleanup
97
+ end
98
+
99
+ # The checksum of the rubocop program running the inspection.
100
+ def rubocop_checksum
101
+ ResultCache.source_checksum ||=
102
+ begin
103
+ lib_root = File.join(File.dirname(__FILE__), '..')
104
+ bin_root = File.join(lib_root, '..', 'bin')
105
+ source = Find.find(lib_root, bin_root).sort.map do |path|
106
+ IO.read(path) if File.file?(path)
107
+ end
108
+ Digest::MD5.hexdigest(source.join)
109
+ end
110
+ end
111
+
112
+ NON_CHANGING = [:color, :format, :formatters, :out, :debug, :fail_level,
113
+ :cache, :fail_fast, :stdin]
114
+
115
+ # Return the options given at invocation, minus the ones that have no
116
+ # effect on which offenses and disabled line ranges are found, and thus
117
+ # don't affect caching.
118
+ def relevant_options(options)
119
+ options = options.reject { |key, _| NON_CHANGING.include?(key) }
120
+ options.to_s.gsub(/[^a-z]+/i, '_')
121
+ end
122
+ end
123
+ end