rubocop 1.6.1 → 1.7.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 (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