rubocop 1.6.1 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +48 -6
  4. data/lib/rubocop.rb +4 -0
  5. data/lib/rubocop/config.rb +8 -5
  6. data/lib/rubocop/config_loader.rb +10 -6
  7. data/lib/rubocop/config_loader_resolver.rb +21 -4
  8. data/lib/rubocop/config_obsoletion.rb +5 -3
  9. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +3 -2
  10. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  11. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +145 -0
  12. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +19 -3
  13. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +14 -0
  14. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +3 -10
  15. data/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb +1 -0
  16. data/lib/rubocop/cop/layout/space_before_block_braces.rb +2 -0
  17. data/lib/rubocop/cop/layout/space_before_brackets.rb +64 -0
  18. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +13 -10
  19. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +2 -2
  20. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +59 -0
  21. data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +7 -2
  22. data/lib/rubocop/cop/lint/duplicate_branch.rb +64 -2
  23. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +50 -17
  24. data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -11
  25. data/lib/rubocop/cop/lint/unreachable_loop.rb +17 -0
  26. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  27. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +59 -5
  28. data/lib/rubocop/cop/registry.rb +10 -0
  29. data/lib/rubocop/cop/style/access_modifier_declarations.rb +3 -1
  30. data/lib/rubocop/cop/style/collection_methods.rb +14 -1
  31. data/lib/rubocop/cop/style/commented_keyword.rb +22 -5
  32. data/lib/rubocop/cop/style/for.rb +2 -0
  33. data/lib/rubocop/cop/style/hash_except.rb +95 -0
  34. data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -2
  35. data/lib/rubocop/cop/style/lambda_call.rb +2 -1
  36. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +3 -0
  37. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +16 -6
  38. data/lib/rubocop/cop/style/method_def_parentheses.rb +7 -0
  39. data/lib/rubocop/cop/style/multiline_method_signature.rb +26 -1
  40. data/lib/rubocop/cop/style/multiline_when_then.rb +3 -1
  41. data/lib/rubocop/cop/style/mutable_constant.rb +13 -3
  42. data/lib/rubocop/cop/style/raise_args.rb +2 -0
  43. data/lib/rubocop/cop/style/redundant_argument.rb +7 -1
  44. data/lib/rubocop/cop/style/redundant_freeze.rb +8 -4
  45. data/lib/rubocop/cop/style/single_line_methods.rb +4 -0
  46. data/lib/rubocop/cop/style/symbol_proc.rb +5 -4
  47. data/lib/rubocop/cop/util.rb +3 -1
  48. data/lib/rubocop/options.rb +9 -9
  49. data/lib/rubocop/rspec/cop_helper.rb +0 -4
  50. data/lib/rubocop/rspec/expect_offense.rb +34 -22
  51. data/lib/rubocop/runner.rb +16 -1
  52. data/lib/rubocop/target_finder.rb +4 -2
  53. data/lib/rubocop/util.rb +16 -0
  54. data/lib/rubocop/version.rb +8 -2
  55. metadata +8 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7e78bdb76ac54d034e71f715292180a9982c1c08096fd8546c52f8786b703bd
4
- data.tar.gz: e94b644cff4449332cd0be3dda1e41d2911f3bfd88fb14862534c0e29736a374
3
+ metadata.gz: fe0d2cef28b14b92436ce28d483594398761fe11474f23e2577630e4c74d877d
4
+ data.tar.gz: 4c06f9d71ffc6b08ad14e9fbb574bcca19cfccc231c36aee75f23c7753643893
5
5
  SHA512:
6
- metadata.gz: 54d96669e8524f0fc27ccc4ed75c44c2ce6c143298ed26affe5739eff1dd3aa9f24d0278e9df8e764c6ceb53b7f868764df783ccfd51d8a9bd633623124e150c
7
- data.tar.gz: f6516450c7473cf56f6d330da24fd2b73827a07603ecda6bba672c470ae7490e97071a118d168f4288e37792289e97cb4503362915e9daa0ce1c79bd1d0def28
6
+ metadata.gz: a395e4004802da434ac704d5aec675b16637d464edc7d9305ec03c739a8d85f57f59d19f31187e93c7f37e9bcd7df6ec0b92df951de8e4508b169637cc7fb190
7
+ data.tar.gz: bac84ae15f62dafabde785bb46bf9e67d4554979fc09a431ee949b61ae03edac8897a6a914f153667eb6abdf803e73efe0a117c691d36d3ff73599ca3b41a13c
data/README.md CHANGED
@@ -51,7 +51,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
51
51
  in your `Gemfile`:
52
52
 
53
53
  ```rb
54
- gem 'rubocop', '~> 1.6', require: false
54
+ gem 'rubocop', '~> 1.7', require: false
55
55
  ```
56
56
 
57
57
  See [versioning](https://docs.rubocop.org/rubocop/1.0/versioning.html) for further details.
@@ -473,7 +473,7 @@ Layout/EmptyLineBetweenDefs:
473
473
  StyleGuide: '#empty-lines-between-methods'
474
474
  Enabled: true
475
475
  VersionAdded: '0.49'
476
- VersionChanged: '1.4'
476
+ VersionChanged: '1.7'
477
477
  EmptyLineBetweenMethodDefs: true
478
478
  EmptyLineBetweenClassDefs: true
479
479
  EmptyLineBetweenModuleDefs: true
@@ -1192,6 +1192,13 @@ Layout/SpaceBeforeBlockBraces:
1192
1192
  - no_space
1193
1193
  VersionChanged: '0.52'
1194
1194
 
1195
+ Layout/SpaceBeforeBrackets:
1196
+ Description: 'Checks for receiver with a space before the opening brackets.'
1197
+ StyleGuide: '#space-in-brackets-access'
1198
+ Enabled: pending
1199
+ VersionAdded: '1.7'
1200
+ Safe: false
1201
+
1195
1202
  Layout/SpaceBeforeComma:
1196
1203
  Description: 'No spaces before commas.'
1197
1204
  Enabled: true
@@ -1354,6 +1361,11 @@ Layout/TrailingWhitespace:
1354
1361
  #################### Lint ##################################
1355
1362
  ### Warnings
1356
1363
 
1364
+ Lint/AmbiguousAssignment:
1365
+ Description: 'Checks for mistyped shorthand assignments.'
1366
+ Enabled: pending
1367
+ VersionAdded: '1.7'
1368
+
1357
1369
  Lint/AmbiguousBlockAssociation:
1358
1370
  Description: >-
1359
1371
  Checks for ambiguous block association with method when param passed without
@@ -1396,6 +1408,7 @@ Lint/BinaryOperatorWithIdenticalOperands:
1396
1408
  Enabled: true
1397
1409
  Safe: false
1398
1410
  VersionAdded: '0.89'
1411
+ VersionChanged: '1.7'
1399
1412
 
1400
1413
  Lint/BooleanSymbol:
1401
1414
  Description: 'Check for `:true` and `:false` symbols.'
@@ -1472,6 +1485,9 @@ Lint/DuplicateBranch:
1472
1485
  Description: Checks that there are no repeated bodies within `if/unless`, `case-when` and `rescue` constructs.
1473
1486
  Enabled: pending
1474
1487
  VersionAdded: '1.3'
1488
+ VersionChanged: '1.7'
1489
+ IgnoreLiteralBranches: false
1490
+ IgnoreConstantBranches: false
1475
1491
 
1476
1492
  Lint/DuplicateCaseCondition:
1477
1493
  Description: 'Do not repeat values in case conditionals.'
@@ -1842,6 +1858,8 @@ Lint/RedundantSplatExpansion:
1842
1858
  Description: 'Checks for splat unnecessarily being called on literals.'
1843
1859
  Enabled: true
1844
1860
  VersionAdded: '0.76'
1861
+ VersionChanged: '1.7'
1862
+ AllowPercentLiteralArrayArgument: true
1845
1863
 
1846
1864
  Lint/RedundantStringCoercion:
1847
1865
  Description: 'Checks for Object#to_s usage in string interpolation.'
@@ -2045,6 +2063,11 @@ Lint/UnreachableLoop:
2045
2063
  Description: 'This cop checks for loops that will have at most one iteration.'
2046
2064
  Enabled: true
2047
2065
  VersionAdded: '0.89'
2066
+ VersionChanged: '1.7'
2067
+ IgnoredPatterns:
2068
+ # RSpec uses `times` in its message expectations
2069
+ # eg. `exactly(2).times`
2070
+ - !ruby/regexp /(exactly|at_least|at_most)\(\d+\)\.times/
2048
2071
 
2049
2072
  Lint/UnusedBlockArgument:
2050
2073
  Description: 'Checks for unused block arguments.'
@@ -2895,7 +2918,7 @@ Style/CollectionMethods:
2895
2918
  StyleGuide: '#map-find-select-reduce-include-size'
2896
2919
  Enabled: false
2897
2920
  VersionAdded: '0.9'
2898
- VersionChanged: '0.27'
2921
+ VersionChanged: '1.7'
2899
2922
  Safe: false
2900
2923
  # Mapping from undesired method to desired method
2901
2924
  # e.g. to use `detect` over `find`:
@@ -2910,6 +2933,11 @@ Style/CollectionMethods:
2910
2933
  detect: 'find'
2911
2934
  find_all: 'select'
2912
2935
  member?: 'include?'
2936
+ # Methods in this array accept a final symbol as an implicit block
2937
+ # eg. `inject(:+)`
2938
+ MethodsAcceptingSymbol:
2939
+ - inject
2940
+ - reduce
2913
2941
 
2914
2942
  Style/ColonMethodCall:
2915
2943
  Description: 'Do not use :: for method call.'
@@ -2969,6 +2997,7 @@ Style/CommentedKeyword:
2969
2997
  Description: 'Do not place comments on the same line as certain keywords.'
2970
2998
  Enabled: true
2971
2999
  VersionAdded: '0.51'
3000
+ VersionChanged: '1.7'
2972
3001
 
2973
3002
  Style/ConditionalAssignment:
2974
3003
  Description: >-
@@ -3328,6 +3357,13 @@ Style/HashEachMethods:
3328
3357
  VersionAdded: '0.80'
3329
3358
  Safe: false
3330
3359
 
3360
+ Style/HashExcept:
3361
+ Description: >-
3362
+ Checks for usages of `Hash#reject`, `Hash#select`, and `Hash#filter` methods
3363
+ that can be replaced with `Hash#except` method.
3364
+ Enabled: pending
3365
+ VersionAdded: '1.7'
3366
+
3331
3367
  Style/HashLikeCase:
3332
3368
  Description: >-
3333
3369
  Checks for places where `case-when` represents a simple 1:1
@@ -3482,6 +3518,7 @@ Style/KeywordParametersOrder:
3482
3518
  StyleGuide: '#keyword-parameters-order'
3483
3519
  Enabled: true
3484
3520
  VersionAdded: '0.90'
3521
+ VersionChanged: '1.7'
3485
3522
 
3486
3523
  Style/Lambda:
3487
3524
  Description: 'Use the new lambda literal syntax for single-line blocks.'
@@ -3520,7 +3557,7 @@ Style/MethodCallWithArgsParentheses:
3520
3557
  StyleGuide: '#method-invocation-parens'
3521
3558
  Enabled: false
3522
3559
  VersionAdded: '0.47'
3523
- VersionChanged: '0.61'
3560
+ VersionChanged: '1.7'
3524
3561
  IgnoreMacros: true
3525
3562
  IgnoredMethods: []
3526
3563
  IgnoredPatterns: []
@@ -3554,7 +3591,7 @@ Style/MethodDefParentheses:
3554
3591
  StyleGuide: '#method-parens'
3555
3592
  Enabled: true
3556
3593
  VersionAdded: '0.16'
3557
- VersionChanged: '0.35'
3594
+ VersionChanged: '1.7'
3558
3595
  EnforcedStyle: require_parentheses
3559
3596
  SupportedStyles:
3560
3597
  - require_parentheses
@@ -3660,6 +3697,7 @@ Style/MultilineMethodSignature:
3660
3697
  Description: 'Avoid multi-line method signatures.'
3661
3698
  Enabled: false
3662
3699
  VersionAdded: '0.59'
3700
+ VersionChanged: '1.7'
3663
3701
 
3664
3702
  Style/MultilineTernaryOperator:
3665
3703
  Description: >-
@@ -4026,12 +4064,16 @@ Style/RedundantArgument:
4026
4064
  Enabled: pending
4027
4065
  Safe: false
4028
4066
  VersionAdded: '1.4'
4029
- VersionChanged: '1.6'
4067
+ VersionChanged: '1.7'
4030
4068
  Methods:
4031
4069
  # Array#join
4032
4070
  join: ''
4033
4071
  # String#split
4034
4072
  split: ' '
4073
+ # String#chomp
4074
+ chomp: "\n"
4075
+ # String#chomp!
4076
+ chomp!: "\n"
4035
4077
 
4036
4078
  Style/RedundantAssignment:
4037
4079
  Description: 'Checks for redundant assignment before returning.'
@@ -4288,7 +4330,7 @@ Style/SingleLineMethods:
4288
4330
  StyleGuide: '#no-single-line-methods'
4289
4331
  Enabled: true
4290
4332
  VersionAdded: '0.9'
4291
- VersionChanged: '0.19'
4333
+ VersionChanged: '1.7'
4292
4334
  AllowIfMethodIsEmpty: true
4293
4335
 
4294
4336
  Style/SlicingWithRange:
@@ -28,6 +28,7 @@ require_relative 'rubocop/name_similarity'
28
28
  require_relative 'rubocop/string_interpreter'
29
29
  require_relative 'rubocop/error'
30
30
  require_relative 'rubocop/warning'
31
+ require_relative 'rubocop/util'
31
32
 
32
33
  require_relative 'rubocop/cop/util'
33
34
  require_relative 'rubocop/cop/offense'
@@ -230,6 +231,7 @@ require_relative 'rubocop/cop/layout/space_around_keyword'
230
231
  require_relative 'rubocop/cop/layout/space_around_method_call_operator'
231
232
  require_relative 'rubocop/cop/layout/space_around_operators'
232
233
  require_relative 'rubocop/cop/layout/space_before_block_braces'
234
+ require_relative 'rubocop/cop/layout/space_before_brackets'
233
235
  require_relative 'rubocop/cop/layout/space_before_comma'
234
236
  require_relative 'rubocop/cop/layout/space_before_comment'
235
237
  require_relative 'rubocop/cop/layout/space_before_first_arg'
@@ -247,6 +249,7 @@ require_relative 'rubocop/cop/layout/space_inside_string_interpolation'
247
249
  require_relative 'rubocop/cop/layout/trailing_empty_lines'
248
250
  require_relative 'rubocop/cop/layout/trailing_whitespace'
249
251
 
252
+ require_relative 'rubocop/cop/lint/ambiguous_assignment'
250
253
  require_relative 'rubocop/cop/lint/ambiguous_block_association'
251
254
  require_relative 'rubocop/cop/lint/ambiguous_operator'
252
255
  require_relative 'rubocop/cop/lint/ambiguous_regexp_literal'
@@ -460,6 +463,7 @@ require_relative 'rubocop/cop/style/global_vars'
460
463
  require_relative 'rubocop/cop/style/guard_clause'
461
464
  require_relative 'rubocop/cop/style/hash_as_last_array_item'
462
465
  require_relative 'rubocop/cop/style/hash_each_methods'
466
+ require_relative 'rubocop/cop/style/hash_except'
463
467
  require_relative 'rubocop/cop/style/hash_like_case'
464
468
  require_relative 'rubocop/cop/style/hash_syntax'
465
469
  require_relative 'rubocop/cop/style/hash_transform_keys'
@@ -50,8 +50,8 @@ module RuboCop
50
50
  self
51
51
  end
52
52
 
53
- def_delegators :@hash, :[], :[]=, :delete, :each, :key?, :keys, :each_key,
54
- :fetch, :map, :merge, :to_h, :to_hash, :transform_values
53
+ def_delegators :@hash, :[], :[]=, :delete, :dig, :each, :key?, :keys, :each_key,
54
+ :fetch, :map, :merge, :replace, :to_h, :to_hash, :transform_values
55
55
  def_delegators :@validator, :validate, :target_ruby_version
56
56
 
57
57
  def to_s
@@ -281,6 +281,9 @@ module RuboCop
281
281
  end
282
282
 
283
283
  def enable_cop?(qualified_cop_name, cop_options)
284
+ # If the cop is explicitly enabled, the other checks can be skipped.
285
+ return true if cop_options['Enabled'] == true
286
+
284
287
  department = department_of(qualified_cop_name)
285
288
  cop_enabled = cop_options.fetch('Enabled') do
286
289
  !for_all_cops['DisabledByDefault']
@@ -292,10 +295,10 @@ module RuboCop
292
295
  end
293
296
 
294
297
  def department_of(qualified_cop_name)
295
- cop_department, cop_name = qualified_cop_name.split('/')
296
- return nil if cop_name.nil?
298
+ *cop_department, _ = qualified_cop_name.split('/')
299
+ return nil if cop_department.empty?
297
300
 
298
- self[cop_department]
301
+ self[cop_department.join('/')]
299
302
  end
300
303
  end
301
304
  end
@@ -31,6 +31,7 @@ module RuboCop
31
31
 
32
32
  def clear_options
33
33
  @debug = nil
34
+ @loaded_features = []
34
35
  FileFinder.root_level = nil
35
36
  end
36
37
 
@@ -177,12 +178,9 @@ module RuboCop
177
178
  @loaded_features.flatten.compact
178
179
  end
179
180
 
180
- private
181
-
182
- def file_path(file)
183
- File.absolute_path(file.is_a?(RemoteConfig) ? file.file : file)
184
- end
185
-
181
+ # @api private
182
+ # Used to add features that were required inside a config or from
183
+ # the CLI using `--require`.
186
184
  def add_loaded_features(loaded_features)
187
185
  if instance_variable_defined?(:@loaded_features)
188
186
  instance_variable_get(:@loaded_features) << loaded_features
@@ -191,6 +189,12 @@ module RuboCop
191
189
  end
192
190
  end
193
191
 
192
+ private
193
+
194
+ def file_path(file)
195
+ File.absolute_path(file.is_a?(RemoteConfig) ? file.file : file)
196
+ end
197
+
194
198
  def find_project_dotfile(target_dir)
195
199
  find_file_upwards(DOTFILE, target_dir, project_root)
196
200
  end
@@ -20,12 +20,13 @@ module RuboCop
20
20
  end
21
21
  end
22
22
 
23
- # rubocop:disable Metrics/MethodLength
24
- def resolve_inheritance(path, hash, file, debug)
23
+ def resolve_inheritance(path, hash, file, debug) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
25
24
  inherited_files = Array(hash['inherit_from'])
26
25
  base_configs(path, inherited_files, file)
27
26
  .reverse.each_with_index do |base_config, index|
28
27
  override_department_setting_for_cops(base_config, hash)
28
+ override_enabled_for_disabled_departments(base_config, hash)
29
+
29
30
  base_config.each do |k, v|
30
31
  next unless v.is_a?(Hash)
31
32
 
@@ -39,7 +40,6 @@ module RuboCop
39
40
  end
40
41
  end
41
42
  end
42
- # rubocop:enable Metrics/MethodLength
43
43
 
44
44
  def resolve_inheritance_from_gems(hash)
45
45
  gems = hash.delete('inherit_gem')
@@ -75,6 +75,7 @@ module RuboCop
75
75
  end
76
76
 
77
77
  config = handle_disabled_by_default(config, default_configuration) if disabled_by_default
78
+ override_enabled_for_disabled_departments(default_configuration, config)
78
79
 
79
80
  opts = { inherit_mode: config['inherit_mode'] || {},
80
81
  unset_nil: unset_nil }
@@ -122,10 +123,26 @@ module RuboCop
122
123
  end
123
124
  end
124
125
 
126
+ # If a cop was previously explicitly enabled, but then superseded by the
127
+ # department being disabled, disable it.
128
+ def override_enabled_for_disabled_departments(base_hash, derived_hash)
129
+ cops_to_disable = derived_hash.each_key.with_object([]) do |key, cops|
130
+ next unless disabled?(derived_hash, key)
131
+
132
+ cops.concat(base_hash.keys.grep(Regexp.new("^#{key}/")))
133
+ end
134
+
135
+ cops_to_disable.each do |cop_name|
136
+ next unless base_hash.dig(cop_name, 'Enabled') == true
137
+
138
+ derived_hash.replace(merge({ cop_name => { 'Enabled' => false } }, derived_hash))
139
+ end
140
+ end
141
+
125
142
  private
126
143
 
127
144
  def disabled?(hash, department)
128
- hash[department] && hash[department]['Enabled'] == false
145
+ hash[department].is_a?(Hash) && hash[department]['Enabled'] == false
129
146
  end
130
147
 
131
148
  def duplicate_setting?(base_hash, derived_hash, key, inherited_file)
@@ -63,9 +63,11 @@ module RuboCop
63
63
  # Cop rules are keyed by the name of the original cop
64
64
  def load_cop_rules(rules)
65
65
  rules.flat_map do |rule_type, data|
66
- data.map do |configuration|
67
- COP_RULE_CLASSES[rule_type].new(@config, *configuration)
68
- end
66
+ data.map do |cop_name, configuration|
67
+ next unless configuration # allow configurations to be disabled with `CopName: ~`
68
+
69
+ COP_RULE_CLASSES[rule_type].new(@config, cop_name, configuration)
70
+ end.compact
69
71
  end
70
72
  end
71
73
 
@@ -36,12 +36,13 @@ module RuboCop
36
36
  # spec.required_ruby_version = '>= 2.5'
37
37
  # end
38
38
  #
39
- # # good
39
+ # # accepted but not recommended
40
40
  # Gem::Specification.new do |spec|
41
41
  # spec.required_ruby_version = ['>= 2.5.0', '< 2.7.0']
42
42
  # end
43
43
  #
44
- # # good
44
+ # # accepted but not recommended, since
45
+ # # Ruby does not really follow semantic versionning
45
46
  # Gem::Specification.new do |spec|
46
47
  # spec.required_ruby_version = '~> 2.5'
47
48
  # end
@@ -6,4 +6,5 @@ require_relative 'internal_affairs/node_type_predicate'
6
6
  require_relative 'internal_affairs/offense_location_keyword'
7
7
  require_relative 'internal_affairs/redundant_message_argument'
8
8
  require_relative 'internal_affairs/redundant_location_argument'
9
+ require_relative 'internal_affairs/style_detected_api_use'
9
10
  require_relative 'internal_affairs/useless_message_assertion'
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # Checks for correct use of the style_detected API provided by
7
+ # `ConfigurableEnforcedStyle`. If `correct_style_detected` is used
8
+ # then `opposite_style_detected`, `unexpected_style_detected`,
9
+ # `ambiguous_style_detected`, `conflicting_styles_detected`,
10
+ # `unrecognized_style_detected` or `no_acceptable_style!` should be
11
+ # used too, and vice versa. The `xxx_style_detected` methods
12
+ # should not be used as predicates either.
13
+ #
14
+ # @example
15
+ #
16
+ # # bad
17
+ # def on_send(node)
18
+ # return add_offense(node) if opposite_style_detected
19
+ #
20
+ # correct_style_detected
21
+ # end
22
+ #
23
+ # def on_send(node)
24
+ # if offense?
25
+ # add_offense(node)
26
+ # else
27
+ # correct_style_detected
28
+ # end
29
+ # end
30
+ #
31
+ # def on_send(node)
32
+ # return unless offense?
33
+ #
34
+ # add_offense(node)
35
+ # opposite_style_detected
36
+ # end
37
+ #
38
+ # # good
39
+ # def on_send(node)
40
+ # if offense?
41
+ # add_offense(node)
42
+ # opposite_style_detected
43
+ # else
44
+ # correct_style_detected
45
+ # end
46
+ # end
47
+ #
48
+ # def on_send(node)
49
+ # add_offense(node) if offense?
50
+ # end
51
+ #
52
+ class StyleDetectedApiUse < Base
53
+ include RangeHelp
54
+
55
+ MSG_FOR_POSITIVE_WITHOUT_NEGATIVE =
56
+ '`correct_style_detected` method called without ' \
57
+ 'calling a negative `*_style_detected` method.'
58
+ MSG_FOR_NEGATIVE_WITHOUT_POSITIVE =
59
+ 'negative `*_style_detected` methods called without ' \
60
+ 'calling `correct_style_detected` method.'
61
+ MSG_FOR_CONDITIONAL_USE =
62
+ '`*_style_detected` method called in conditional.'
63
+ RESTRICT_ON_SEND = %i[
64
+ correct_style_detected opposite_style_detected
65
+ unexpected_style_detected ambiguous_style_detected
66
+ conflicting_styles_detected unrecognized_style_detected
67
+ no_acceptable_style! style_detected
68
+ ].freeze
69
+
70
+ def_node_matcher :correct_style_detected_check, <<~PATTERN
71
+ (send nil? :correct_style_detected)
72
+ PATTERN
73
+
74
+ def_node_matcher :negative_style_detected_method_check, <<~PATTERN
75
+ (send nil? /(?:opposite|unexpected|ambiguous|unrecognized)_style_detected|conflicting_styles_detected/ ...)
76
+ PATTERN
77
+
78
+ def_node_matcher :no_acceptable_style_check, <<~PATTERN
79
+ (send nil? :no_acceptable_style!)
80
+ PATTERN
81
+
82
+ def_node_matcher :style_detected_check, <<~PATTERN
83
+ (send nil? :style_detected ...)
84
+ PATTERN
85
+
86
+ def on_new_investigation
87
+ @correct_style_detected_called = false
88
+ @negative_style_detected_methods_called = false
89
+ @style_detected_called = false
90
+ end
91
+
92
+ def on_investigation_end
93
+ return if style_detected_called
94
+ return unless correct_style_detected_called ^ negative_style_detected_methods_called
95
+
96
+ add_global_offense(MSG_FOR_POSITIVE_WITHOUT_NEGATIVE) if positive_without_negative?
97
+ add_global_offense(MSG_FOR_NEGATIVE_WITHOUT_POSITIVE) if negative_without_positive?
98
+ end
99
+
100
+ def on_send(node)
101
+ if correct_style_detected_check(node)
102
+ @correct_style_detected_called = true
103
+ elsif negative_style_detected_method_check(node) || no_acceptable_style_check(node)
104
+ @negative_style_detected_methods_called = true
105
+ elsif style_detected_check(node)
106
+ @style_detected_called = true
107
+ end
108
+ end
109
+
110
+ def on_if(node)
111
+ traverse_condition(node.condition) do |cond|
112
+ add_offense(cond, message: MSG_FOR_CONDITIONAL_USE) if style_detected_api_used?(cond)
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ attr_reader :correct_style_detected_called,
119
+ :negative_style_detected_methods_called,
120
+ :style_detected_called
121
+
122
+ def positive_without_negative?
123
+ correct_style_detected_called && !negative_style_detected_methods_called
124
+ end
125
+
126
+ def negative_without_positive?
127
+ negative_style_detected_methods_called && !correct_style_detected_called
128
+ end
129
+
130
+ def style_detected_api_used?(node)
131
+ correct_style_detected_check(node) ||
132
+ negative_style_detected_method_check(node) ||
133
+ no_acceptable_style_check(node) ||
134
+ style_detected_check(node)
135
+ end
136
+
137
+ def traverse_condition(condition, &block)
138
+ yield condition if condition.send_type?
139
+
140
+ condition.each_child_node { |child| traverse_condition(child, &block) }
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end