rubocop 1.88.2 → 1.89.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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +67 -11
  3. data/lib/rubocop/comment_config.rb +35 -19
  4. data/lib/rubocop/config.rb +53 -0
  5. data/lib/rubocop/config_validator.rb +38 -1
  6. data/lib/rubocop/cop/badge.rb +8 -0
  7. data/lib/rubocop/cop/base.rb +6 -4
  8. data/lib/rubocop/cop/bundler.rb +19 -0
  9. data/lib/rubocop/cop/corrector.rb +1 -1
  10. data/lib/rubocop/cop/correctors/alignment_corrector.rb +55 -5
  11. data/lib/rubocop/cop/gemspec.rb +22 -0
  12. data/lib/rubocop/cop/generator/registration_injector.rb +188 -0
  13. data/lib/rubocop/cop/generator.rb +4 -0
  14. data/lib/rubocop/cop/layout/class_structure.rb +8 -2
  15. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +1 -1
  16. data/lib/rubocop/cop/layout/indentation_width.rb +3 -1
  17. data/lib/rubocop/cop/layout/leading_comment_space.rb +27 -0
  18. data/lib/rubocop/cop/layout/line_length.rb +85 -12
  19. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +12 -2
  20. data/lib/rubocop/cop/layout/redundant_line_break.rb +77 -0
  21. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +33 -34
  22. data/lib/rubocop/cop/layout.rb +114 -0
  23. data/lib/rubocop/cop/lazy_loader.rb +41 -0
  24. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +9 -7
  25. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +71 -0
  26. data/lib/rubocop/cop/lint/constant_reassignment.rb +1 -10
  27. data/lib/rubocop/cop/lint/constant_resolution.rb +25 -0
  28. data/lib/rubocop/cop/lint/deprecated_reference.rb +185 -0
  29. data/lib/rubocop/cop/lint/duplicate_methods.rb +202 -22
  30. data/lib/rubocop/cop/lint/inherit_exception.rb +53 -7
  31. data/lib/rubocop/cop/lint/literal_as_condition.rb +5 -0
  32. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
  33. data/lib/rubocop/cop/lint/missing_super.rb +31 -1
  34. data/lib/rubocop/cop/lint/name_typo.rb +223 -0
  35. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +0 -4
  36. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +7 -0
  37. data/lib/rubocop/cop/lint/to_enum_arguments.rb +37 -1
  38. data/lib/rubocop/cop/lint/unused_private_method.rb +157 -0
  39. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +2 -0
  40. data/lib/rubocop/cop/lint/void.rb +17 -6
  41. data/lib/rubocop/cop/lint.rb +176 -0
  42. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  43. data/lib/rubocop/cop/metrics.rb +31 -0
  44. data/lib/rubocop/cop/migration/department_name.rb +0 -4
  45. data/lib/rubocop/cop/migration.rb +13 -0
  46. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -5
  47. data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +13 -5
  48. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +23 -6
  49. data/lib/rubocop/cop/mixin/project_index_help.rb +140 -0
  50. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +171 -0
  51. data/lib/rubocop/cop/mixin/statement_modifier.rb +42 -6
  52. data/lib/rubocop/cop/mixin.rb +78 -77
  53. data/lib/rubocop/cop/naming/accessor_method_name.rb +24 -0
  54. data/lib/rubocop/cop/naming/predicate_prefix.rb +33 -5
  55. data/lib/rubocop/cop/naming/variable_number.rb +3 -1
  56. data/lib/rubocop/cop/naming.rb +31 -0
  57. data/lib/rubocop/cop/offense.rb +10 -6
  58. data/lib/rubocop/cop/registry.rb +153 -43
  59. data/lib/rubocop/cop/security.rb +19 -0
  60. data/lib/rubocop/cop/style/array_intersect.rb +24 -5
  61. data/lib/rubocop/cop/style/class_and_module_children.rb +78 -18
  62. data/lib/rubocop/cop/style/conditional_assignment.rb +22 -0
  63. data/lib/rubocop/cop/style/copyright.rb +6 -1
  64. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +41 -3
  65. data/lib/rubocop/cop/style/documentation.rb +47 -0
  66. data/lib/rubocop/cop/style/endless_method.rb +0 -10
  67. data/lib/rubocop/cop/style/if_unless_modifier.rb +7 -48
  68. data/lib/rubocop/cop/style/lambda.rb +4 -0
  69. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +20 -3
  70. data/lib/rubocop/cop/style/missing_respond_to_missing.rb +30 -0
  71. data/lib/rubocop/cop/style/multiline_if_then.rb +7 -1
  72. data/lib/rubocop/cop/style/multiple_comparison.rb +33 -22
  73. data/lib/rubocop/cop/style/mutable_constant.rb +5 -0
  74. data/lib/rubocop/cop/style/negative_array_index.rb +3 -1
  75. data/lib/rubocop/cop/style/redundant_assignment.rb +11 -0
  76. data/lib/rubocop/cop/style/redundant_constant_base.rb +37 -1
  77. data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +9 -0
  78. data/lib/rubocop/cop/style/redundant_format.rb +12 -3
  79. data/lib/rubocop/cop/style/redundant_line_continuation.rb +78 -135
  80. data/lib/rubocop/cop/style/redundant_parentheses.rb +63 -63
  81. data/lib/rubocop/cop/style/safe_navigation.rb +10 -0
  82. data/lib/rubocop/cop/style/sole_nested_conditional.rb +20 -3
  83. data/lib/rubocop/cop/style/static_class.rb +21 -0
  84. data/lib/rubocop/cop/style.rb +310 -0
  85. data/lib/rubocop/cop/team.rb +48 -3
  86. data/lib/rubocop/cop/variable_force/variable.rb +18 -6
  87. data/lib/rubocop/cops_documentation_generator.rb +1 -1
  88. data/lib/rubocop/directive_comment.rb +2 -0
  89. data/lib/rubocop/feature_loader.rb +0 -6
  90. data/lib/rubocop/lsp/routes.rb +41 -3
  91. data/lib/rubocop/lsp/runtime.rb +4 -0
  92. data/lib/rubocop/lsp/server.rb +4 -0
  93. data/lib/rubocop/lsp/stdin_runner.rb +20 -0
  94. data/lib/rubocop/pending_cops_reporter.rb +1 -6
  95. data/lib/rubocop/project_index_loader.rb +16 -0
  96. data/lib/rubocop/result_cache.rb +6 -4
  97. data/lib/rubocop/rspec/shared_contexts.rb +14 -2
  98. data/lib/rubocop/runner.rb +45 -20
  99. data/lib/rubocop/server/cache.rb +10 -5
  100. data/lib/rubocop/target_ruby.rb +1 -1
  101. data/lib/rubocop/version.rb +1 -1
  102. data/lib/rubocop.rb +17 -626
  103. metadata +18 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8642dc64b958118e0563ed93c4837ac13aca29cb8f8a6267155ef450380ea1b3
4
- data.tar.gz: 569ff18924510b28b60ff03563053d0c53983829d7f4755c0ad39cc213b41f64
3
+ metadata.gz: e613534eb66c6766aa04d7e1075e0d09aa8a3a78690b8b921583d81ea9fb8e9e
4
+ data.tar.gz: c65ec7b8f764105d8950cc73c4deca24f6bec976c6c4041b5338f4a97e98645c
5
5
  SHA512:
6
- metadata.gz: 1cc966f73a8b890c335fedbc55fa0ec0f8a73099703cf46111e8619933f2d4b9436cf1c7d597f7fef0282e1e1e2f6b6adc4b4e38c7195eabb600fe97d9931526
7
- data.tar.gz: 94cc97c23eeaaff0ec7ab9046a2275547fe2363449d0582733e30be26868e098d12c609703fdc2318b1e4d96b8205abf6fe537d655950cf558debfcb6224366c
6
+ metadata.gz: 400677d5633740bb086f3f6e4c6cb9438423ce5a77ea6bc8a4bd72ff0249332c58370bbd98f809673699806cc2ae8dfde4d95a9313bfb8e465c631b506a18d20
7
+ data.tar.gz: a08736ea293673d3cebd8185df6b8b0cf718d24d23b772c20c09c1c00e61838c4963245e84074fe0b8732ad5eb32f9f459c3d915fb5a07ad9ee0e0019d2b9a2c
data/config/default.yml CHANGED
@@ -110,6 +110,12 @@ AllCops:
110
110
  # a warning is shown and the flag has no effect.
111
111
  # The default `false` preserves RuboCop's traditional file-local analysis.
112
112
  UseProjectIndex: false
113
+ # When the project index is enabled, also index the sources of the gems in
114
+ # the project's bundle. This lets index-aware cops resolve ancestry chains
115
+ # and members that live in gems (e.g. framework base classes), at the cost
116
+ # of extra memory proportional to the bundle's size. Has no effect when
117
+ # `UseProjectIndex` is `false` or when RuboCop does not run inside Bundler.
118
+ ProjectIndexIncludesGems: false
113
119
  # Enables the result cache if `true`. Can be overridden by the `--cache` command
114
120
  # line option.
115
121
  UseCache: true
@@ -165,6 +171,7 @@ AllCops:
165
171
  rubocop-capybara: [capybara]
166
172
  rubocop-factory_bot: [factory_bot, factory_bot_rails]
167
173
  rubocop-rspec_rails: [rspec-rails]
174
+ rubocop-i18n: [i18n, gettext]
168
175
  # Enable/Disable checking the methods extended by Active Support.
169
176
  ActiveSupportExtensionsEnabled: false
170
177
  # Future version of Ruby will freeze string literals by default.
@@ -511,7 +518,7 @@ Layout/ClassStructure:
511
518
  Enabled: false
512
519
  SafeAutoCorrect: false
513
520
  VersionAdded: '0.52'
514
- VersionChanged: '1.53'
521
+ VersionChanged: '1.89'
515
522
  Categories:
516
523
  module_inclusion:
517
524
  - include
@@ -524,6 +531,7 @@ Layout/ClassStructure:
524
531
  - initializer
525
532
  - public_methods
526
533
  - protected_methods
534
+ - private_class_methods
527
535
  - private_methods
528
536
 
529
537
  Layout/ClosingHeredocIndentation:
@@ -1069,6 +1077,7 @@ Layout/LeadingCommentSpace:
1069
1077
  AllowGemfileRubyComment: false
1070
1078
  AllowRBSInlineAnnotation: false
1071
1079
  AllowSteepAnnotation: false
1080
+ AllowYARDCommentBlockSeparator: false
1072
1081
 
1073
1082
  Layout/LeadingEmptyLines:
1074
1083
  Description: Checks for unnecessary blank lines at the beginning of a file.
@@ -1615,7 +1624,7 @@ Lint/AmbiguousBlockAssociation:
1615
1624
  parentheses.
1616
1625
  Enabled: true
1617
1626
  VersionAdded: '0.48'
1618
- VersionChanged: '1.13'
1627
+ VersionChanged: '1.89'
1619
1628
  AllowedMethods: []
1620
1629
  AllowedPatterns: []
1621
1630
 
@@ -1713,6 +1722,7 @@ Lint/ConstantReassignment:
1713
1722
 
1714
1723
  Lint/ConstantResolution:
1715
1724
  Description: 'Checks that constants are fully qualified with `::`.'
1725
+ VersionChanged: '1.89'
1716
1726
  Enabled: false
1717
1727
  VersionAdded: '0.86'
1718
1728
  # Restrict this cop to only looking at certain names
@@ -1829,6 +1839,14 @@ Lint/DeprecatedOpenSSLConstant:
1829
1839
  Enabled: true
1830
1840
  VersionAdded: '0.84'
1831
1841
 
1842
+ Lint/DeprecatedReference:
1843
+ Description: >-
1844
+ Checks for references to methods and constants documented
1845
+ as deprecated with a YARD `@deprecated` tag.
1846
+ Requires `AllCops/UseProjectIndex` to be enabled.
1847
+ Enabled: pending
1848
+ VersionAdded: '1.89'
1849
+
1832
1850
  Lint/DisjunctiveAssignmentInConstructor:
1833
1851
  Description: 'In constructor, plain assignment is preferred over disjunctive.'
1834
1852
  Enabled: true
@@ -1875,6 +1893,14 @@ Lint/DuplicateMethods:
1875
1893
  Description: 'Checks for duplicate method definitions.'
1876
1894
  Enabled: true
1877
1895
  VersionAdded: '0.29'
1896
+ VersionChanged: '1.89'
1897
+ # Methods that define instance methods by delegation, in the style of
1898
+ # Active Support's `delegate` (`delegate :foo, :bar, to: :target`). Register
1899
+ # a project's own `delegate`-shaped macros here so the cop recognizes the
1900
+ # methods they define. Only takes effect when `AllCops/ActiveSupportExtensionsEnabled`
1901
+ # is `true`.
1902
+ DelegatingMethods:
1903
+ - delegate
1878
1904
 
1879
1905
  Lint/DuplicateRegexpCharacterClassElement:
1880
1906
  Description: 'Checks for duplicate elements in Regexp character classes.'
@@ -2057,7 +2083,7 @@ Lint/InheritException:
2057
2083
  Enabled: true
2058
2084
  SafeAutoCorrect: false
2059
2085
  VersionAdded: '0.41'
2060
- VersionChanged: '1.26'
2086
+ VersionChanged: '1.89'
2061
2087
  # The default base class in favour of `Exception`.
2062
2088
  EnforcedStyle: standard_error
2063
2089
  SupportedStyles:
@@ -2130,7 +2156,7 @@ Lint/MissingSuper:
2130
2156
  without calls to `super`.
2131
2157
  Enabled: true
2132
2158
  VersionAdded: '0.89'
2133
- VersionChanged: '1.4'
2159
+ VersionChanged: '1.89'
2134
2160
  AllowedParentClasses: []
2135
2161
 
2136
2162
  Lint/MixedCaseRange:
@@ -2150,6 +2176,16 @@ Lint/MultipleComparison:
2150
2176
  VersionAdded: '0.47'
2151
2177
  VersionChanged: '1.1'
2152
2178
 
2179
+ Lint/NameTypo:
2180
+ Description: >-
2181
+ Checks for probable typos in constant and method names,
2182
+ using the project index.
2183
+ Enabled: pending
2184
+ CheckConstants: true
2185
+ CheckMethods: true
2186
+ AllowedNames: []
2187
+ VersionAdded: '1.89'
2188
+
2153
2189
  Lint/NestedMethodDefinition:
2154
2190
  Description: 'Do not use nested method definitions.'
2155
2191
  StyleGuide: '#no-nested-methods'
@@ -2409,8 +2445,9 @@ Lint/ReturnInVoidContext:
2409
2445
  Lint/SafeNavigationChain:
2410
2446
  Description: 'Do not chain ordinary method call after safe navigation operator.'
2411
2447
  Enabled: true
2448
+ SafeAutoCorrect: false
2412
2449
  VersionAdded: '0.47'
2413
- VersionChanged: '0.77'
2450
+ VersionChanged: '1.89'
2414
2451
  AllowedMethods:
2415
2452
  - present?
2416
2453
  - blank?
@@ -2632,6 +2669,18 @@ Lint/UnusedMethodArgument:
2632
2669
  NotImplementedExceptions:
2633
2670
  - NotImplementedError
2634
2671
 
2672
+ Lint/UnusedPrivateMethod:
2673
+ Description: >-
2674
+ Checks for private instance methods that are not referenced
2675
+ anywhere in the project. Requires `AllCops/UseProjectIndex`
2676
+ to be enabled.
2677
+ # This cop is disabled by default because symbol-based references from other
2678
+ # files (e.g. Rails callbacks declared in a concern) cannot be detected and
2679
+ # would be reported as false positives. It is intended for occasional
2680
+ # dead-code sweeps rather than permanent enforcement.
2681
+ Enabled: false
2682
+ VersionAdded: '1.89'
2683
+
2635
2684
  Lint/UriEscapeUnescape:
2636
2685
  Description: >-
2637
2686
  `URI.escape` method is obsolete and should not be used. Instead, use
@@ -2863,6 +2912,7 @@ Migration/DepartmentName:
2863
2912
  Naming/AccessorMethodName:
2864
2913
  Description: Checks the naming of accessor methods for get_/set_.
2865
2914
  StyleGuide: '#accessor_mutator_method_names'
2915
+ VersionChanged: '1.89'
2866
2916
  Enabled: true
2867
2917
  VersionAdded: '0.50'
2868
2918
 
@@ -3139,7 +3189,7 @@ Naming/PredicatePrefix:
3139
3189
  StyleGuide: '#bool-methods-qmark'
3140
3190
  Enabled: true
3141
3191
  VersionAdded: '0.50'
3142
- VersionChanged: '1.75'
3192
+ VersionChanged: '1.89'
3143
3193
  # Predicate name prefixes.
3144
3194
  NamePrefix:
3145
3195
  - is_
@@ -3607,13 +3657,14 @@ Style/ClassAndModuleChildren:
3607
3657
  StyleGuide: '#namespace-definition'
3608
3658
  # Moving from compact to nested children requires knowledge of whether the
3609
3659
  # outer parent is a module or a class. Moving from nested to compact requires
3610
- # verification that the outer parent is defined elsewhere. RuboCop does not
3611
- # have the knowledge to perform either operation safely and thus requires
3612
- # manual oversight.
3660
+ # verification that the outer parent is defined elsewhere. By default RuboCop
3661
+ # does not have the knowledge to perform either operation safely and thus
3662
+ # requires manual oversight. When `AllCops/UseProjectIndex` is enabled, the
3663
+ # project-wide index is consulted for both operations.
3613
3664
  SafeAutoCorrect: false
3614
3665
  Enabled: true
3615
3666
  VersionAdded: '0.19'
3616
- VersionChanged: '1.74'
3667
+ VersionChanged: '1.89'
3617
3668
  #
3618
3669
  # Basically there are two different styles:
3619
3670
  #
@@ -3925,8 +3976,9 @@ Style/DisableCopsWithinSourceCodeDirective:
3925
3976
  Forbids disabling/enabling cops within source code.
3926
3977
  Enabled: false
3927
3978
  VersionAdded: '0.82'
3928
- VersionChanged: '1.9'
3979
+ VersionChanged: '1.89'
3929
3980
  AllowedCops: []
3981
+ DisallowedCops: []
3930
3982
 
3931
3983
  Style/DocumentDynamicEvalDefinition:
3932
3984
  Description: >-
@@ -3941,6 +3993,7 @@ Style/Documentation:
3941
3993
  Description: 'Document classes and non-namespace modules.'
3942
3994
  Enabled: true
3943
3995
  VersionAdded: '0.9'
3996
+ VersionChanged: '1.89'
3944
3997
  AllowedConstants: []
3945
3998
  Exclude:
3946
3999
  - 'spec/**/*'
@@ -4778,6 +4831,7 @@ Style/MissingRespondToMissing:
4778
4831
  StyleGuide: '#no-method-missing'
4779
4832
  Enabled: true
4780
4833
  VersionAdded: '0.56'
4834
+ VersionChanged: '1.89'
4781
4835
 
4782
4836
  Style/MixinGrouping:
4783
4837
  Description: 'Checks for grouping of mixins in `class` and `module` bodies.'
@@ -5397,6 +5451,7 @@ Style/RedundantConditional:
5397
5451
 
5398
5452
  Style/RedundantConstantBase:
5399
5453
  Description: Avoid redundant `::` prefix on constant.
5454
+ VersionChanged: '1.89'
5400
5455
  Enabled: pending
5401
5456
  VersionAdded: '1.40'
5402
5457
 
@@ -5830,6 +5885,7 @@ Style/StabbyLambdaParentheses:
5830
5885
  Style/StaticClass:
5831
5886
  Description: 'Prefer modules to classes with only class methods.'
5832
5887
  StyleGuide: '#modules-vs-classes'
5888
+ VersionChanged: '1.89'
5833
5889
  Enabled: false
5834
5890
  Safe: false
5835
5891
  VersionAdded: '1.3'
@@ -55,8 +55,8 @@ module RuboCop
55
55
 
56
56
  def extra_enabled_comments
57
57
  disable_count = Hash.new(0)
58
- registry.disabled(config).each do |cop|
59
- disable_count[cop.cop_name] += 1
58
+ registry.disabled_names(config).each do |cop_name|
59
+ disable_count[cop_name] += 1
60
60
  end
61
61
  extra_enabled_comments_with_names(extras: Hash.new { |h, k| h[k] = [] }, names: disable_count)
62
62
  end
@@ -65,6 +65,24 @@ module RuboCop
65
65
  non_comment_token_line_numbers.none?(line_number)
66
66
  end
67
67
 
68
+ # The names of the cops that are opted in by an `enable` comment directive,
69
+ # used to mobilize cops disabled in the config on demand.
70
+ #
71
+ # @api private
72
+ # @return [Set<String>]
73
+ def opt_in_cops
74
+ @opt_in_cops ||= begin
75
+ cops = Set.new
76
+ each_directive do |directive|
77
+ next unless directive.enabled?
78
+ next if directive.all_cops?
79
+
80
+ cops.merge(directive.raw_cop_names)
81
+ end
82
+ cops
83
+ end
84
+ end
85
+
68
86
  private
69
87
 
70
88
  def extra_enabled_comments_with_names(extras:, names:)
@@ -82,19 +100,6 @@ module RuboCop
82
100
  extras
83
101
  end
84
102
 
85
- def opt_in_cops
86
- @opt_in_cops ||= begin
87
- cops = Set.new
88
- each_directive do |directive|
89
- next unless directive.enabled?
90
- next if directive.all_cops?
91
-
92
- cops.merge(directive.raw_cop_names)
93
- end
94
- cops
95
- end
96
- end
97
-
98
103
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
99
104
  def analyze
100
105
  return {} if @no_directives
@@ -119,6 +124,8 @@ module RuboCop
119
124
 
120
125
  analyses.each_with_object({}) do |element, hash|
121
126
  cop_name, analysis = *element
127
+ next if prevent_directive_disabling?(cop_name)
128
+
122
129
  hash[cop_name] = cop_line_ranges(analysis)
123
130
  end
124
131
  end
@@ -163,10 +170,10 @@ module RuboCop
163
170
  end
164
171
 
165
172
  def inject_disabled_cops_directives(analyses)
166
- registry.disabled(config).each do |cop|
167
- analyses[cop.cop_name] = analyze_cop(
168
- analyses[cop.cop_name],
169
- DirectiveComment.new(ConfigDisabledCopDirectiveComment.new(cop.cop_name))
173
+ registry.disabled_names(config).each do |cop_name|
174
+ analyses[cop_name] = analyze_cop(
175
+ analyses[cop_name],
176
+ DirectiveComment.new(ConfigDisabledCopDirectiveComment.new(cop_name))
170
177
  )
171
178
  end
172
179
  end
@@ -222,6 +229,15 @@ module RuboCop
222
229
  Cop::Registry.qualified_cop_name(cop_name.strip, processed_source.file_path)
223
230
  end
224
231
 
232
+ # `Style/DisableCopsWithinSourceCodeDirective` cannot be disabled via
233
+ # directive comments when it is explicitly enabled with `Enabled: true`.
234
+ # This prevents users from bypassing the cop by writing a disable
235
+ # directive that targets this cop itself.
236
+ def prevent_directive_disabling?(cop_name)
237
+ cop_name == DirectiveComment::STYLE_DISABLE_COPS_DIRECTIVE_COP &&
238
+ config.dig(cop_name, 'Enabled') == true
239
+ end
240
+
225
241
  def non_comment_token_line_numbers
226
242
  @non_comment_token_line_numbers ||= begin
227
243
  non_comment_tokens = processed_source.tokens.reject(&:comment?)
@@ -209,6 +209,20 @@ module RuboCop
209
209
  for_all_cops['NewCops'] == 'enable'
210
210
  end
211
211
 
212
+ # Whether the given pending cop should be enabled, based on the `NewCops` setting of
213
+ # its department (if any) or of `AllCops`. A department may set `NewCops` to `enable`,
214
+ # `disable`, `pending`, or a version, in which case pending cops added in that version
215
+ # or earlier are enabled.
216
+ def enabled_new_cop?(qualified_cop_name)
217
+ setting = new_cops_setting_for(qualified_cop_name)
218
+
219
+ case setting.to_s
220
+ when 'enable' then true
221
+ when '', 'pending', 'disable' then false
222
+ else new_cops_version_covers?(setting, qualified_cop_name)
223
+ end
224
+ end
225
+
212
226
  def active_support_extensions_enabled?
213
227
  for_all_cops['ActiveSupportExtensionsEnabled']
214
228
  end
@@ -326,6 +340,7 @@ module RuboCop
326
340
 
327
341
  cop_metadata = self[qualified_cop_name]
328
342
  next unless cop_metadata['Enabled'] == 'pending'
343
+ next if new_cops_covered?(qualified_cop_name)
329
344
 
330
345
  pending_cops << CopConfig.new(qualified_cop_name, cop_metadata)
331
346
  end
@@ -399,6 +414,44 @@ module RuboCop
399
414
 
400
415
  self[cop_department.join('/')]
401
416
  end
417
+
418
+ # The effective `NewCops` setting for the given cop: the department-level
419
+ # setting if present, otherwise the `AllCops` setting.
420
+ def new_cops_setting_for(qualified_cop_name)
421
+ department = department_of(qualified_cop_name)
422
+ setting = department['NewCops'] if department
423
+
424
+ setting || for_all_cops['NewCops']
425
+ end
426
+
427
+ # Whether the cop's pending status is resolved by a `NewCops` setting,
428
+ # so that it should not appear in the pending cops warning.
429
+ # Unlike `enabled_new_cop?`, `disable` counts as covered.
430
+ def new_cops_covered?(qualified_cop_name)
431
+ setting = new_cops_setting_for(qualified_cop_name)
432
+
433
+ case setting.to_s
434
+ when 'enable', 'disable' then true
435
+ when '', 'pending' then false
436
+ else new_cops_version_covers?(setting, qualified_cop_name)
437
+ end
438
+ end
439
+
440
+ def new_cops_version_covers?(new_cops_version, qualified_cop_name)
441
+ cop_metadata = self[qualified_cop_name]
442
+ version_added = comparable_version(cop_metadata['VersionAdded']) if cop_metadata
443
+ pinned_version = comparable_version(new_cops_version)
444
+ return false if version_added.nil? || pinned_version.nil?
445
+
446
+ version_added <= pinned_version
447
+ end
448
+
449
+ # Returns a `Gem::Version` for values like `'1.19'` or `1.19`, and `nil`
450
+ # for non-version values like `'N/A'` or `'<<next>>'`.
451
+ def comparable_version(value)
452
+ value = value.to_s
453
+ Gem::Version.new(value) if value.match?(/\A\d/) && Gem::Version.correct?(value)
454
+ end
402
455
  end
403
456
  end
404
457
  # rubocop:enable Metrics/ClassLength
@@ -16,6 +16,8 @@ module RuboCop
16
16
  Reference References Safe SafeAutoCorrect].freeze
17
17
  # @api private
18
18
  NEW_COPS_VALUES = %w[pending disable enable].freeze
19
+ # @api private
20
+ NEW_COPS_VERSION_PATTERN = /\A\d+(\.\d+)*\z/.freeze
19
21
 
20
22
  # @api private
21
23
  CONFIG_CHECK_KEYS = %w[Enabled Safe SafeAutoCorrect AutoCorrect References].to_set.freeze
@@ -164,16 +166,48 @@ module RuboCop
164
166
  end
165
167
 
166
168
  def validate_new_cops_parameter
169
+ validate_all_cops_new_cops_parameter
170
+ validate_department_new_cops_parameters
171
+ end
172
+
173
+ def validate_all_cops_new_cops_parameter
167
174
  new_cop_parameter = @config.for_all_cops['NewCops']
168
175
  return if new_cop_parameter.nil? || NEW_COPS_VALUES.include?(new_cop_parameter)
169
176
 
170
- message = "invalid #{new_cop_parameter} for `NewCops` found in" \
177
+ message = "invalid #{new_cop_parameter} for `NewCops` found in " \
171
178
  "#{smart_loaded_path}\n" \
172
179
  "Valid choices are: #{NEW_COPS_VALUES.join(', ')}"
173
180
 
174
181
  raise ValidationError, message
175
182
  end
176
183
 
184
+ def validate_department_new_cops_parameters
185
+ @config.each do |name, section|
186
+ value = new_cops_value_for_department(name, section)
187
+ next if value.nil? || NEW_COPS_VALUES.include?(value) || new_cops_version_value?(value)
188
+
189
+ raise ValidationError,
190
+ "invalid #{value} for `NewCops` found in #{smart_loaded_path}\n" \
191
+ "Valid choices for a department are: #{NEW_COPS_VALUES.join(', ')}, " \
192
+ "or a version string like '1.50'"
193
+ end
194
+ end
195
+
196
+ def new_cops_value_for_department(name, section)
197
+ return nil if name == 'AllCops' || !section.is_a?(Hash)
198
+ return nil unless Cop::Registry.global.department?(name)
199
+
200
+ section['NewCops']
201
+ end
202
+
203
+ def new_cops_version_value?(value)
204
+ case value
205
+ when Float, Integer then true
206
+ when String then NEW_COPS_VERSION_PATTERN.match?(value)
207
+ else false
208
+ end
209
+ end
210
+
177
211
  def validate_parameter_shape(valid_cop_names)
178
212
  valid_cop_names.each do |name|
179
213
  if @config[name].nil?
@@ -207,6 +241,9 @@ module RuboCop
207
241
 
208
242
  @config[cop_name].each_key do |param|
209
243
  next if COMMON_PARAMS.include?(param) || default_config.key?(param)
244
+ # Departments shipped as top-level sections in an extension's default configuration accept
245
+ # `NewCops`, like any other department.
246
+ next if param == 'NewCops' && Cop::Registry.global.department?(cop_name)
210
247
 
211
248
  supported_params = default_config.keys - INTERNAL_PARAMS
212
249
 
@@ -52,6 +52,14 @@ module RuboCop
52
52
  cop_name == other.cop_name && (!qualified? || department == other.department)
53
53
  end
54
54
 
55
+ # Returns true if the badge's qualified name or the badge's department
56
+ # matches any of the given names.
57
+ def match_name?(given_names)
58
+ return false unless given_names
59
+
60
+ given_names.include?(to_s) || given_names.include?(department_name)
61
+ end
62
+
55
63
  def to_s
56
64
  @to_s ||= qualified? ? "#{department}/#{cop_name}" : cop_name
57
65
  end
@@ -372,6 +372,12 @@ module RuboCop
372
372
 
373
373
  private
374
374
 
375
+ ### Reserved for Commissioner
376
+
377
+ private_class_method def self.restrict_on_send
378
+ @restrict_on_send ||= self::RESTRICT_ON_SEND.to_a.freeze
379
+ end
380
+
375
381
  ### Reserved for Cop::Cop
376
382
 
377
383
  def callback_argument(range)
@@ -400,10 +406,6 @@ module RuboCop
400
406
  @current_offenses ||= []
401
407
  end
402
408
 
403
- private_class_method def self.restrict_on_send
404
- @restrict_on_send ||= self::RESTRICT_ON_SEND.to_a.freeze
405
- end
406
-
407
409
  EMPTY_OFFENSES = [].freeze
408
410
  private_constant :EMPTY_OFFENSES
409
411
  # Called to complete an investigation
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Cops for the `Bundler` department. The department's cops are registered for lazy loading
6
+ # and their files are loaded on demand.
7
+ module Bundler
8
+ extend LazyLoader
9
+
10
+ register_cop :DuplicatedGem, "#{__dir__}/bundler/duplicated_gem"
11
+ register_cop :DuplicatedGroup, "#{__dir__}/bundler/duplicated_group"
12
+ register_cop :GemComment, "#{__dir__}/bundler/gem_comment"
13
+ register_cop :GemFilename, "#{__dir__}/bundler/gem_filename"
14
+ register_cop :GemVersion, "#{__dir__}/bundler/gem_version"
15
+ register_cop :InsecureProtocolSource, "#{__dir__}/bundler/insecure_protocol_source"
16
+ register_cop :OrderedGems, "#{__dir__}/bundler/ordered_gems"
17
+ end
18
+ end
19
+ end
@@ -103,7 +103,7 @@ module RuboCop
103
103
  # :nodoc:
104
104
  def to_range(node_or_range)
105
105
  range = case node_or_range
106
- when ::RuboCop::AST::Node, ::Parser::Source::Comment
106
+ when RuboCop::AST::Node, ::Parser::Source::Comment
107
107
  node_or_range.source_range
108
108
  when ::Parser::Source::Range
109
109
  node_or_range
@@ -12,20 +12,28 @@ module RuboCop
12
12
  class << self
13
13
  attr_reader :processed_source
14
14
 
15
- def correct(corrector, processed_source, node, column_delta)
15
+ # `tab_indentation` may only be set by callers whose `column_delta` represents
16
+ # whole indentation levels (e.g. `Layout/IndentationWidth`).
17
+ # Alignment to an arbitrary column cannot be expressed with tabs, so those callers
18
+ # leave tab correction disabled to avoid producing a never-converging correction.
19
+ def correct(corrector, processed_source, node, column_delta, tab_indentation: false)
16
20
  return unless node
17
21
 
18
22
  @processed_source = processed_source
19
- # Disable autocorrection for tabs as it requires special handling
20
- return if using_tabs?
21
23
 
22
24
  expr = node.respond_to?(:loc) ? node.source_range : node
23
25
  return if block_comment_within?(expr)
24
26
 
25
27
  taboo_ranges = inside_string_ranges(node)
26
28
 
27
- each_line(expr) do |line_begin_pos|
28
- autocorrect_line(corrector, line_begin_pos, expr, column_delta, taboo_ranges)
29
+ if using_tabs?
30
+ return unless tab_indentation
31
+
32
+ correct_tab_indentation(corrector, expr, column_delta, taboo_ranges)
33
+ else
34
+ each_line(expr) do |line_begin_pos|
35
+ autocorrect_line(corrector, line_begin_pos, expr, column_delta, taboo_ranges)
36
+ end
29
37
  end
30
38
  end
31
39
 
@@ -58,6 +66,48 @@ module RuboCop
58
66
  end
59
67
  end
60
68
 
69
+ # Tab indentation is corrected by rewriting each line's leading whitespace to
70
+ # the target number of tabs. Working in whole tabs rather than applying a column delta
71
+ # keeps the result idempotent, which avoids the infinite loops that delta-based correction
72
+ # caused for tabs.
73
+ def correct_tab_indentation(corrector, expr, column_delta, taboo_ranges)
74
+ buffer = expr.source_buffer
75
+
76
+ each_line(expr) do |line_begin_pos|
77
+ line_range = buffer.line_range(buffer.line_for_position(line_begin_pos))
78
+ correct_tab_line(corrector, line_range, column_delta, taboo_ranges)
79
+ end
80
+ end
81
+
82
+ def correct_tab_line(corrector, line_range, column_delta, taboo_ranges)
83
+ leading = line_range.source[/\A[ \t]*/]
84
+ return if leading.length == line_range.source.length # blank line
85
+
86
+ leading_range = range_between(line_range.begin_pos, line_range.begin_pos + leading.length)
87
+ return if taboo_ranges.any? { |t| within?(leading_range, t) }
88
+
89
+ target = target_tab_indentation(leading, column_delta)
90
+ corrector.replace(leading_range, target) unless leading == target
91
+ end
92
+
93
+ def target_tab_indentation(leading, column_delta)
94
+ width = configured_indentation_width
95
+ return leading unless width.positive?
96
+
97
+ visual_width = leading.chars.sum { |char| char == "\t" ? width : 1 }
98
+
99
+ "\t" * ([visual_width + column_delta, 0].max / width)
100
+ end
101
+
102
+ # The number of columns a single tab spans. This must match the width
103
+ # `Layout/IndentationWidth` uses to compute `column_delta`, otherwise
104
+ # the division below would not land on a tab boundary. `Layout/IndentationStyle`
105
+ # has its own `IndentationWidth`, but that only governs how that cop replaces tabs
106
+ # with spaces, so it must not be consulted here.
107
+ def configured_indentation_width
108
+ processed_source.config.for_cop('Layout/IndentationWidth')['Width'] || 2
109
+ end
110
+
61
111
  def inside_string_ranges(node)
62
112
  return [] unless node.is_a?(Parser::AST::Node)
63
113
 
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Cops for the `Gemspec` department. The department's cops are registered for lazy loading
6
+ # and their files are loaded on demand.
7
+ module Gemspec
8
+ extend LazyLoader
9
+
10
+ register_cop :AddRuntimeDependency, "#{__dir__}/gemspec/add_runtime_dependency"
11
+ register_cop :AttributeAssignment, "#{__dir__}/gemspec/attribute_assignment"
12
+ register_cop :DependencyVersion, "#{__dir__}/gemspec/dependency_version"
13
+ register_cop :DeprecatedAttributeAssignment, "#{__dir__}/gemspec/deprecated_attribute_assignment"
14
+ register_cop :DevelopmentDependencies, "#{__dir__}/gemspec/development_dependencies"
15
+ register_cop :DuplicatedAssignment, "#{__dir__}/gemspec/duplicated_assignment"
16
+ register_cop :OrderedDependencies, "#{__dir__}/gemspec/ordered_dependencies"
17
+ register_cop :RequireMFA, "#{__dir__}/gemspec/require_mfa"
18
+ register_cop :RequiredRubyVersion, "#{__dir__}/gemspec/required_ruby_version"
19
+ register_cop :RubyVersionGlobalsUsage, "#{__dir__}/gemspec/ruby_version_globals_usage"
20
+ end
21
+ end
22
+ end