diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 4994e6ccb565..2a401681b39c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -39,7 +39,7 @@ output by `rubocop -V`, include them as well. Here's an example: ``` $ [bundle exec] rubocop -V -1.73.2 (using Parser 3.3.5.0, rubocop-ast 1.32.3, analyzing as Ruby 3.3, running on ruby 3.3.5) [x86_64-linux] +1.74.0 (using Parser 3.3.5.0, rubocop-ast 1.32.3, analyzing as Ruby 3.3, running on ruby 3.3.5) [x86_64-linux] - rubocop-performance 1.22.1 - rubocop-rspec 3.1.0 ``` diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index 126ce604f8ef..ca700d5628b2 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -52,10 +52,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: - # FIXME: When the JRuby issue https://github.com/jruby/jruby/issues/8606 is resolved, - # please replace it with the following: - # ruby-version: 'jruby' # Latest stable JRuby version - ruby-version: 'jruby-9.4.10.0' # The latest JRuby version that passes CI + ruby-version: 'jruby' # Latest stable JRuby version bundler-cache: true - name: spec run: bundle exec rake spec diff --git a/CHANGELOG.md b/CHANGELOG.md index da0490f2b859..a91a53dd029e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,36 @@ ## master (unreleased) +## 1.74.0 (2025-03-13) + +### New features + +* [#13936](https://github.com/rubocop/rubocop/pull/13936): Adds new cop `Style/ComparableBetween`. ([@lovro-bikic][]) +* [#13943](https://github.com/rubocop/rubocop/pull/13943): Allow writing steep annotation to method definition for `Style/CommentedKeyword`. ([@dak2][]) + +### Bug fixes + +* [#13969](https://github.com/rubocop/rubocop/issues/13969): Fix a false positive for `Lint/SharedMutableDefault` when `capacity` keyword argument is used. ([@koic][]) +* [#13945](https://github.com/rubocop/rubocop/pull/13945): Fix a false positive for `Style/DoubleNegation` when calling `define_method`/`define_singleton_method` with a numblock. ([@earlopain][]) +* [#13971](https://github.com/rubocop/rubocop/pull/13971): Fix false alarm for config obsoletion. ([@koic][]) +* [#13960](https://github.com/rubocop/rubocop/pull/13960): Fix a false negative for `Lint/ReturnInVoidContext` when returning out of a block. ([@earlopain][]) +* [#13947](https://github.com/rubocop/rubocop/pull/13947): Fix a false negative for `Lint/UselessConstantScoping` for constants defined in `class << self`. ([@earlopain][]) +* [#13949](https://github.com/rubocop/rubocop/pull/13949): Fix a false negative for `Lint/NonLocalExitFromIterator` with numblocks. ([@earlopain][]) +* [#13975](https://github.com/rubocop/rubocop/issues/13975): Fix false positives for `Style/RedundantCurrentDirectoryInPath` when using a complex current directory path in `require_relative`. ([@koic][]) +* [#13963](https://github.com/rubocop/rubocop/issues/13963): Fix wrong autocorrect for `Lint/LiteralAsCondition` when the literal is followed by `return`, `break`, or `next`. ([@earlopain][]) +* [#13946](https://github.com/rubocop/rubocop/pull/13946): Fix some false positives for `Style/MethodCallWithArgsParentheses` with `EnforcedStyle: omit_parentheses` style and numblocks. ([@earlopain][]) +* [#13950](https://github.com/rubocop/rubocop/pull/13950): Fix sporadic errors about `rubocop-rails` or `rubocop-performance` extraction, even if they are already part of the Gemfile. ([@earlopain][]) +* [#13981](https://github.com/rubocop/rubocop/pull/13981): Prevent redundant plugin loading when a duplicate plugin is specified in an inherited config. ([@koic][]) +* [#13965](https://github.com/rubocop/rubocop/issues/13965): Update `Lint/RedundantCopDisableDirective` to register an offense when cop names are given with improper casing. ([@dvandersluis][]) +* [#13948](https://github.com/rubocop/rubocop/pull/13948): Fix wrong autocorrect for `Style/RescueModifier` when using parallel assignment and the right-hand-side is not a bracketed array. ([@earlopain][]) + +### Changes + +* [#12851](https://github.com/rubocop/rubocop/issues/12851): Add `EnforcedStyleForClasses` and `EnforcedStyleForModules` configuration options to `Style/ClassAndModuleChildren`. ([@dvandersluis][]) +* [#13979](https://github.com/rubocop/rubocop/pull/13979): Add `Mode: conservative` configuration to `Style/FormatStringToken` to make the cop only register offenses for strings given to `printf`, `sprintf`, `format`, and `%`. ([@dvandersluis][]) +* [#13977](https://github.com/rubocop/rubocop/issues/13977): Allow `TLS1_1` and `TLS1_2` by default in `Naming/VariableNumber` to accommodate OpenSSL version parameter names. ([@koic][]) +* [#13967](https://github.com/rubocop/rubocop/pull/13967): Make `Lint/RedundantTypeConversion` aware of redundant `to_d`. ([@koic][]) + ## 1.73.2 (2025-03-04) ### Bug fixes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f503b2ed5239..9b3d41f61b8a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,7 @@ do so. ```console $ rubocop -V -1.73.2 (using Parser 3.3.5.0, rubocop-ast 1.32.3, analyzing as Ruby 3.3, running on ruby 3.3.5) [x86_64-linux] +1.74.0 (using Parser 3.3.5.0, rubocop-ast 1.32.3, analyzing as Ruby 3.3, running on ruby 3.3.5) [x86_64-linux] - rubocop-performance 1.22.1 - rubocop-rspec 3.1.0 ``` diff --git a/README.md b/README.md index b44affe73981..94b5c39f6b7a 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi in your `Gemfile`: ```rb -gem 'rubocop', '~> 1.73', require: false +gem 'rubocop', '~> 1.74', require: false ``` See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details. diff --git a/config/default.yml b/config/default.yml index 37f4be2cb400..cf1f10b45b0f 100644 --- a/config/default.yml +++ b/config/default.yml @@ -3104,6 +3104,8 @@ Naming/VariableNumber: CheckMethodNames: true CheckSymbols: true AllowedIdentifiers: + - TLS1_1 # OpenSSL::SSL::TLS1_1_VERSION + - TLS1_2 # OpenSSL::SSL::TLS1_2_VERSION - capture3 # Open3.capture3 - iso8601 # Time#iso8601 - rfc1123_date # CGI.rfc1123_date @@ -3499,6 +3501,7 @@ Style/ClassAndModuleChildren: SafeAutoCorrect: false Enabled: true VersionAdded: '0.19' + VersionChanged: '1.74' # # Basically there are two different styles: # @@ -3514,7 +3517,21 @@ Style/ClassAndModuleChildren: # # The compact style is only forced, for classes or modules with one child. EnforcedStyle: nested - SupportedStyles: + SupportedStyles: &supported_styles + - nested + - compact + # Configure classes separately, if desired. If not set, or set to `nil`, + # the `EnforcedStyle` value will be used. + EnforcedStyleForClasses: ~ + SupportedStylesForClasses: + - ~ + - nested + - compact + # Configure modules separately, if desired. If not set, or set to `nil`, + # the `EnforcedStyle` value will be used. + EnforcedStyleForModules: ~ + SupportedStylesForModules: + - ~ - nested - compact @@ -3667,6 +3684,12 @@ Style/CommentedKeyword: VersionAdded: '0.51' VersionChanged: '1.19' +Style/ComparableBetween: + Description: 'Enforces the use of `Comparable#between?` instead of logical comparison.' + Enabled: pending + VersionAdded: '1.74' + StyleGuide: '#ranges-or-between' + Style/ComparableClamp: Description: 'Enforces the use of `Comparable#clamp` instead of comparison by minimum and maximum.' Enabled: pending @@ -4069,8 +4092,14 @@ Style/FormatStringToken: # style token in a format string to be allowed when enforced style is not # `unannotated`. MaxUnannotatedPlaceholdersAllowed: 1 + # The mode the cop operates in. Two values are allowed: + # * aggressive (default): all strings are considered + # * conservative: + # only register offenses for strings given to `printf`, `sprintf`, + # format` and `%` methods. Other strings are not considered. + Mode: aggressive VersionAdded: '0.49' - VersionChanged: '1.0' + VersionChanged: '1.74' AllowedMethods: [] AllowedPatterns: [] diff --git a/docs/antora.yml b/docs/antora.yml index 0ec0cdbc5d57..69f60481ad3a 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -2,6 +2,6 @@ name: rubocop title: RuboCop # We always provide version without patch here (e.g. 1.1), # as patch versions should not appear in the docs. -version: '1.73' +version: '1.74' nav: - modules/ROOT/nav.adoc diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index 0e63bd0bce2a..8ad42e71fd58 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -438,6 +438,7 @@ In the following section you find all available cops: * xref:cops_style.adoc#stylecommandliteral[Style/CommandLiteral] * xref:cops_style.adoc#stylecommentannotation[Style/CommentAnnotation] * xref:cops_style.adoc#stylecommentedkeyword[Style/CommentedKeyword] +* xref:cops_style.adoc#stylecomparablebetween[Style/ComparableBetween] * xref:cops_style.adoc#stylecomparableclamp[Style/ComparableClamp] * xref:cops_style.adoc#styleconcatarrayliterals[Style/ConcatArrayLiterals] * xref:cops_style.adoc#styleconditionalassignment[Style/ConditionalAssignment] diff --git a/docs/modules/ROOT/pages/cops_lint.adoc b/docs/modules/ROOT/pages/cops_lint.adoc index f3a1aa8e9661..a2af5268eb07 100644 --- a/docs/modules/ROOT/pages/cops_lint.adoc +++ b/docs/modules/ROOT/pages/cops_lint.adoc @@ -5456,13 +5456,13 @@ warn something | - |=== -Checks for redundant uses of `to_s`, `to_sym`, `to_i`, `to_f`, `to_r`, `to_c`, +Checks for redundant uses of `to_s`, `to_sym`, `to_i`, `to_f`, `to_d`, `to_r`, `to_c`, `to_a`, `to_h`, and `to_set`. When one of these methods is called on an object of the same type, that object is returned, making the call unnecessary. The cop detects conversion methods called on object literals, class constructors, class `[]` methods, and the `Kernel` methods -`String()`, `Integer()`, `Float()`, `Rational()`, `Complex()` and `Array()`. +`String()`, `Integer()`, `Float()`, BigDecimal(), `Rational()`, `Complex()`, and `Array()`. Specifically, these cases are detected for each conversion method: @@ -8108,8 +8108,8 @@ end |=== Checks for useless constant scoping. Private constants must be defined using -`private_constant` or `class << self`. Even if `private` access modifier is used, -it is public scope despite its appearance. +`private_constant`. Even if `private` access modifier is used, it is public scope despite +its appearance. It does not support autocorrection due to behavior change and multiple ways to fix it. Or a public constant may be intended. @@ -8131,14 +8131,6 @@ class Foo private_constant :PRIVATE_CONST end -# good -class Foo - class << self - private - PRIVATE_CONST = 42 - end -end - # good class Foo PUBLIC_CONST = 42 # If private scope is not intended. diff --git a/docs/modules/ROOT/pages/cops_naming.adoc b/docs/modules/ROOT/pages/cops_naming.adoc index 136acb8cdeb7..8a4dc8e2cc65 100644 --- a/docs/modules/ROOT/pages/cops_naming.adoc +++ b/docs/modules/ROOT/pages/cops_naming.adoc @@ -1733,7 +1733,7 @@ expect(Open3).to receive(:capture3) | Boolean | AllowedIdentifiers -| `capture3`, `iso8601`, `rfc1123_date`, `rfc822`, `rfc2822`, `rfc3339`, `x86_64` +| `TLS1_1`, `TLS1_2`, `capture3`, `iso8601`, `rfc1123_date`, `rfc822`, `rfc2822`, `rfc3339`, `x86_64` | Array | AllowedPatterns diff --git a/docs/modules/ROOT/pages/cops_style.adoc b/docs/modules/ROOT/pages/cops_style.adoc index 6abb5be0741d..891a2f28e5ec 100644 --- a/docs/modules/ROOT/pages/cops_style.adoc +++ b/docs/modules/ROOT/pages/cops_style.adoc @@ -1752,11 +1752,23 @@ That's a good use case of ? literal so it doesn't count it as an offense. | Yes | Always (Unsafe) | 0.19 -| - +| 1.74 |=== -Checks the style of children definitions at classes and -modules. Basically there are two different styles: +Checks that namespaced classes and modules are defined with a consistent style. + +With `nested` style, classes and modules should be defined separately (one constant +on each line, without `::`). With `compact` style, classes and modules should be +defined with fully qualified names (using `::` for namespaces). + +NOTE: The style chosen will affect `Module.nesting` for the class or module. Using +`nested` style will result in each level being added, whereas `compact` style will +only include the fully qualified class or module name. + +By default, `EnforcedStyle` applies to both classes and modules. If desired, separate +styles can be defined for classes and modules by using `EnforcedStyleForClasses` and +`EnforcedStyleForModules` respectively. If not set, or set to nil, the `EnforcedStyle` +value will be used. The compact style is only forced for classes/modules with one child. @@ -1765,8 +1777,8 @@ The compact style is only forced for classes/modules with one child. Autocorrection is unsafe. -Moving from compact to nested children requires knowledge of whether the -outer parent is a module or a class. Moving from nested to compact requires +Moving from `compact` to `nested` children requires knowledge of whether the +outer parent is a module or a class. Moving from `nested` to `compact` requires verification that the outer parent is defined elsewhere. RuboCop does not have the knowledge to perform either operation safely and thus requires manual oversight. @@ -1807,6 +1819,14 @@ end | EnforcedStyle | `nested` | `nested`, `compact` + +| EnforcedStyleForClasses +| `` +| ``, `nested`, `compact` + +| EnforcedStyleForModules +| `` +| ``, `nested`, `compact` |=== [#references-styleclassandmodulechildren] @@ -2775,8 +2795,8 @@ Checks for comments put on the same line as some keywords. These keywords are: `class`, `module`, `def`, `begin`, `end`. Note that some comments -(`:nodoc:`, `:yields:`, `rubocop:disable` and `rubocop:todo`) -and RBS::Inline annotation comments are allowed. +(`:nodoc:`, `:yields:`, `rubocop:disable` and `rubocop:todo`), +RBS::Inline annotation, and Steep annotation (`steep:ignore`) are allowed. Autocorrection removes comments from `end` keyword and keeps comments for `class`, `module`, `def` and `begin` above the keyword. @@ -2816,6 +2836,45 @@ class X # :nodoc: end ---- +[#stylecomparablebetween] +== Style/ComparableBetween + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| Always +| 1.74 +| - +|=== + +Checks for logical comparison which can be replaced with `Comparable#between?`. + +NOTE: `Comparable#between?` is on average slightly slower than logical comparison, +although the difference generally isn't observable. If you require maximum +performance, consider using logical comparison. + +[#examples-stylecomparablebetween] +=== Examples + +[source,ruby] +---- +# bad +x >= min && x <= max + +# bad +x <= max && x >= min + +# good +x.between?(min, max) +---- + +[#references-stylecomparablebetween] +=== References + +* https://rubystyle.guide#ranges-or-between + [#stylecomparableclamp] == Style/ComparableClamp @@ -5787,19 +5846,27 @@ puts '%10s' % 'foo' | Yes | Always | 0.49 -| 1.0 +| 1.74 |=== -Use a consistent style for named format string tokens. +Use a consistent style for tokens within a format string. -NOTE: `unannotated` style cop only works for strings -which are passed as arguments to those methods: -`printf`, `sprintf`, `format`, `%`. -The reason is that _unannotated_ format is very similar -to encoded URLs or Date/Time formatting strings. +By default, all strings are evaluated. In some cases, this may be undesirable, +as they could be used as arguments to a method that does not consider +them to be tokens, but rather other identifiers or just part of the string. -This cop's allowed methods can be customized with `AllowedMethods`. -By default, there are no allowed methods. +`AllowedMethods` or `AllowedPatterns` can be configured with in order to mark specific +methods as always allowed, thereby avoiding an offense from the cop. By default, there +are no allowed methods. + +Additionally, the cop can be made conservative by configuring it with +`Mode: conservative` (default `aggressive`). In this mode, tokens (regardless +of `EnforcedStyle`) are only considered if used in the format string argument to the +methods `printf`, `sprintf`, `format` and `%`. + +NOTE: Tokens in the `unannotated` style (eg. `%s`) are always treated as if +configured with `Conservative: true`. This is done in order to prevent false positives, +because this format is very similar to encoded URLs or Date/Time formatting strings. It is allowed to contain unannotated token if the number of them is less than or equals to @@ -5908,6 +5975,25 @@ redirect('foo/%{bar_id}') redirect('foo/%{bar_id}') ---- +[#mode_-conservative_-enforcedstyle_-annotated-styleformatstringtoken] +==== Mode: conservative, EnforcedStyle: annotated + +[source,ruby] +---- +# In `conservative` mode, offenses are only registered for strings +# given to a known formatting method. + +# good +"%{greeting}" +foo("%{greeting}") + +# bad +format("%{greeting}", greeting: 'Hello') +printf("%{greeting}", greeting: 'Hello') +sprintf("%{greeting}", greeting: 'Hello') +"%{greeting}" % { greeting: 'Hello' } +---- + [#configurable-attributes-styleformatstringtoken] === Configurable attributes @@ -5922,6 +6008,10 @@ redirect('foo/%{bar_id}') | `1` | Integer +| Mode +| `aggressive` +| String + | AllowedMethods | `[]` | Array diff --git a/docs/modules/ROOT/pages/extensions.adoc b/docs/modules/ROOT/pages/extensions.adoc index 11256a85b9e1..7e57194f516b 100644 --- a/docs/modules/ROOT/pages/extensions.adoc +++ b/docs/modules/ROOT/pages/extensions.adoc @@ -1,6 +1,6 @@ = Extensions -:lint_roller: https://github.com/standardrb/lint_roller[lint_roller]. +:lint_roller: https://github.com/standardrb/lint_roller[lint_roller] It's possible to extend RuboCop with additional cops and formatters. There are already many official extensions, maintained by RuboCop's team, diff --git a/docs/modules/ROOT/pages/installation.adoc b/docs/modules/ROOT/pages/installation.adoc index 7397758c172e..2905859cbe7e 100644 --- a/docs/modules/ROOT/pages/installation.adoc +++ b/docs/modules/ROOT/pages/installation.adoc @@ -22,7 +22,7 @@ in your `Gemfile`: [source,rb] ---- -gem 'rubocop', '~> 1.73', require: false +gem 'rubocop', '~> 1.74', require: false ---- .A Modular RuboCop diff --git a/docs/modules/ROOT/pages/integration_with_other_tools.adoc b/docs/modules/ROOT/pages/integration_with_other_tools.adoc index 0270a958373a..8ec8304e1d1f 100644 --- a/docs/modules/ROOT/pages/integration_with_other_tools.adoc +++ b/docs/modules/ROOT/pages/integration_with_other_tools.adoc @@ -125,7 +125,7 @@ following to your `.pre-commit-config.yaml` file: [source,yaml] ---- - repo: https://github.com/rubocop/rubocop - rev: v1.73.2 + rev: v1.74.0 hooks: - id: rubocop ---- @@ -136,7 +136,7 @@ entries in `additional_dependencies`: [source,yaml] ---- - repo: https://github.com/rubocop/rubocop - rev: v1.73.2 + rev: v1.74.0 hooks: - id: rubocop additional_dependencies: diff --git a/lib/rubocop.rb b/lib/rubocop.rb index 01eb817eefdb..0ba1cada53aa 100644 --- a/lib/rubocop.rb +++ b/lib/rubocop.rb @@ -509,6 +509,7 @@ require_relative 'rubocop/cop/style/command_literal' require_relative 'rubocop/cop/style/comment_annotation' require_relative 'rubocop/cop/style/commented_keyword' +require_relative 'rubocop/cop/style/comparable_between' require_relative 'rubocop/cop/style/comparable_clamp' require_relative 'rubocop/cop/style/concat_array_literals' require_relative 'rubocop/cop/style/conditional_assignment' diff --git a/lib/rubocop/config_loader_resolver.rb b/lib/rubocop/config_loader_resolver.rb index ee27a12f0699..5d727f06cdfa 100644 --- a/lib/rubocop/config_loader_resolver.rb +++ b/lib/rubocop/config_loader_resolver.rb @@ -9,7 +9,8 @@ module RuboCop # @api private class ConfigLoaderResolver # rubocop:disable Metrics/ClassLength def resolve_plugins(rubocop_config, plugins) - return if (plugins = Array(plugins)).empty? + plugins = Array(plugins) - ConfigLoader.loaded_plugins.map { |plugin| plugin.about.name } + return if plugins.empty? Plugin.integrate_plugins(rubocop_config, plugins) end diff --git a/lib/rubocop/config_obsoletion.rb b/lib/rubocop/config_obsoletion.rb index 5cc6f3d0c808..f81abafb9d80 100644 --- a/lib/rubocop/config_obsoletion.rb +++ b/lib/rubocop/config_obsoletion.rb @@ -50,7 +50,7 @@ def reject_obsolete! # Default rules for obsoletions are in config/obsoletion.yml # Additional rules files can be added with `RuboCop::ConfigObsoletion.files << filename` def load_rules # rubocop:disable Metrics/AbcSize - rules = LOAD_RULES_CACHE[self.class.files] ||= + rules = LOAD_RULES_CACHE[self.class.files.hash] ||= self.class.files.each_with_object({}) do |filename, hash| hash.merge!(YAML.safe_load(File.read(filename)) || {}) do |_key, first, second| case first diff --git a/lib/rubocop/config_obsoletion/extracted_cop.rb b/lib/rubocop/config_obsoletion/extracted_cop.rb index b744ef604fae..650eaf13238f 100644 --- a/lib/rubocop/config_obsoletion/extracted_cop.rb +++ b/lib/rubocop/config_obsoletion/extracted_cop.rb @@ -15,7 +15,7 @@ def initialize(config, old_name, gem) end def violated? - return false if feature_loaded? + return false if plugin_loaded? affected_cops.any? end @@ -38,8 +38,9 @@ def affected_cops end end - def feature_loaded? - config.loaded_features.include?(gem) + def plugin_loaded? + # Plugins loaded via `require` are included in `loaded_features`. + config.loaded_plugins.include?(gem) || config.loaded_features.include?(gem) end end end diff --git a/lib/rubocop/cop/lint/literal_as_condition.rb b/lib/rubocop/cop/lint/literal_as_condition.rb index 74f5ce7ebd73..c1219740fb0d 100644 --- a/lib/rubocop/cop/lint/literal_as_condition.rb +++ b/lib/rubocop/cop/lint/literal_as_condition.rb @@ -46,6 +46,10 @@ def on_and(node) return unless node.lhs.truthy_literal? add_offense(node.lhs) do |corrector| + # Don't autocorrect `'foo' && return` because having `return` as + # the leftmost node can lead to a void value expression syntax error. + next if node.rhs.type?(:return, :break, :next) + corrector.replace(node, node.rhs.source) end end diff --git a/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb b/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb index 07c3ce041c79..08121ed8aaba 100644 --- a/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +++ b/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb @@ -46,7 +46,7 @@ class NonLocalExitFromIterator < Base def on_return(return_node) return if return_value?(return_node) - return_node.each_ancestor(:block, :def, :defs) do |node| + return_node.each_ancestor(:any_block, :def, :defs) do |node| break if scoped_node?(node) # if a proc is passed to `Module#define_method` or @@ -54,7 +54,7 @@ def on_return(return_node) # non-local exit error break if define_method?(node.send_node) - next unless node.arguments? + next if node.argument_list.empty? if chained_send?(node.send_node) add_offense(return_node.loc.keyword) diff --git a/lib/rubocop/cop/lint/redundant_type_conversion.rb b/lib/rubocop/cop/lint/redundant_type_conversion.rb index be1e9b388d9c..f65a7595011c 100644 --- a/lib/rubocop/cop/lint/redundant_type_conversion.rb +++ b/lib/rubocop/cop/lint/redundant_type_conversion.rb @@ -3,13 +3,13 @@ module RuboCop module Cop module Lint - # Checks for redundant uses of `to_s`, `to_sym`, `to_i`, `to_f`, `to_r`, `to_c`, + # Checks for redundant uses of `to_s`, `to_sym`, `to_i`, `to_f`, `to_d`, `to_r`, `to_c`, # `to_a`, `to_h`, and `to_set`. # # When one of these methods is called on an object of the same type, that object # is returned, making the call unnecessary. The cop detects conversion methods called # on object literals, class constructors, class `[]` methods, and the `Kernel` methods - # `String()`, `Integer()`, `Float()`, `Rational()`, `Complex()` and `Array()`. + # `String()`, `Integer()`, `Float()`, BigDecimal(), `Rational()`, `Complex()`, and `Array()`. # # Specifically, these cases are detected for each conversion method: # @@ -98,6 +98,7 @@ class RedundantTypeConversion < Base to_s: 'string_constructor?', to_i: 'integer_constructor?', to_f: 'float_constructor?', + to_d: 'bigdecimal_constructor?', to_r: 'rational_constructor?', to_c: 'complex_constructor?', to_a: 'array_constructor?', @@ -110,7 +111,7 @@ class RedundantTypeConversion < Base TYPED_METHODS = { to_s: %i[inspect] }.freeze CONVERSION_METHODS = Set[*LITERAL_NODE_TYPES.keys].freeze - RESTRICT_ON_SEND = CONVERSION_METHODS + RESTRICT_ON_SEND = CONVERSION_METHODS + [:to_d] private_constant :LITERAL_NODE_TYPES, :CONSTRUCTOR_MAPPING @@ -137,6 +138,11 @@ class RedundantTypeConversion < Base #type_constructor?(:Float) PATTERN + # @!method bigdecimal_constructor?(node) + def_node_matcher :bigdecimal_constructor?, <<~PATTERN + #type_constructor?(:BigDecimal) + PATTERN + # @!method rational_constructor?(node) def_node_matcher :rational_constructor?, <<~PATTERN #type_constructor?(:Rational) diff --git a/lib/rubocop/cop/lint/return_in_void_context.rb b/lib/rubocop/cop/lint/return_in_void_context.rb index 1c930fbe81a0..ff1f6acdac60 100644 --- a/lib/rubocop/cop/lint/return_in_void_context.rb +++ b/lib/rubocop/cop/lint/return_in_void_context.rb @@ -35,22 +35,15 @@ class ReturnInVoidContext < Base def on_return(return_node) return unless return_node.descendants.any? - context_node = non_void_context(return_node) - - return unless context_node&.def_type? - return unless context_node&.void_context? + def_node = return_node.each_ancestor(:def).first + return unless def_node&.void_context? + return if return_node.each_ancestor(:any_block).any?(&:lambda?) add_offense( return_node.loc.keyword, - message: format(message, method: context_node.method_name) + message: format(message, method: def_node.method_name) ) end - - private - - def non_void_context(return_node) - return_node.each_ancestor(:block, :def, :defs).first - end end end end diff --git a/lib/rubocop/cop/lint/shared_mutable_default.rb b/lib/rubocop/cop/lint/shared_mutable_default.rb index 9a6b4378bad4..be9bd4b24ebb 100644 --- a/lib/rubocop/cop/lint/shared_mutable_default.rb +++ b/lib/rubocop/cop/lint/shared_mutable_default.rb @@ -51,7 +51,18 @@ class SharedMutableDefault < Base # @!method hash_initialized_with_mutable_shared_object?(node) def_node_matcher :hash_initialized_with_mutable_shared_object?, <<~PATTERN - (send (const {nil? cbase} :Hash) :new {array hash (send (const {nil? cbase} {:Array :Hash}) :new)}) + { + (send (const {nil? cbase} :Hash) :new [ + {array hash (send (const {nil? cbase} {:Array :Hash}) :new)} + !#capacity_keyword_argument? + ]) + (send (const {nil? cbase} :Hash) :new hash #capacity_keyword_argument?) + } + PATTERN + + # @!method capacity_keyword_argument?(node) + def_node_matcher :capacity_keyword_argument?, <<~PATTERN + (hash (pair (sym :capacity) _)) PATTERN def on_send(node) diff --git a/lib/rubocop/cop/lint/useless_constant_scoping.rb b/lib/rubocop/cop/lint/useless_constant_scoping.rb index 5a214c00f782..078b8b5e7a21 100644 --- a/lib/rubocop/cop/lint/useless_constant_scoping.rb +++ b/lib/rubocop/cop/lint/useless_constant_scoping.rb @@ -4,8 +4,8 @@ module RuboCop module Cop module Lint # Checks for useless constant scoping. Private constants must be defined using - # `private_constant` or `class << self`. Even if `private` access modifier is used, - # it is public scope despite its appearance. + # `private_constant`. Even if `private` access modifier is used, it is public scope despite + # its appearance. # # It does not support autocorrection due to behavior change and multiple ways to fix it. # Or a public constant may be intended. @@ -26,14 +26,6 @@ module Lint # # # good # class Foo - # class << self - # private - # PRIVATE_CONST = 42 - # end - # end - # - # # good - # class Foo # PUBLIC_CONST = 42 # If private scope is not intended. # end # @@ -46,7 +38,6 @@ class UselessConstantScoping < Base PATTERN def on_casgn(node) - return if node.each_ancestor(:sclass).any? return unless after_private_modifier?(node.left_siblings) return if private_constantize?(node.right_siblings, node.name) diff --git a/lib/rubocop/cop/mixin/check_single_line_suitability.rb b/lib/rubocop/cop/mixin/check_single_line_suitability.rb index 0c89d443e5c1..3d1dcb256ed1 100644 --- a/lib/rubocop/cop/mixin/check_single_line_suitability.rb +++ b/lib/rubocop/cop/mixin/check_single_line_suitability.rb @@ -35,7 +35,7 @@ def comment_within?(node) comment_line_numbers = processed_source.comments.map { |comment| comment.loc.line } comment_line_numbers.any? do |comment_line_number| - comment_line_number >= node.first_line && comment_line_number <= node.last_line + comment_line_number.between?(node.first_line, node.last_line) end end diff --git a/lib/rubocop/cop/mixin/target_ruby_version.rb b/lib/rubocop/cop/mixin/target_ruby_version.rb index 7c332f9671f7..e986a8687cab 100644 --- a/lib/rubocop/cop/mixin/target_ruby_version.rb +++ b/lib/rubocop/cop/mixin/target_ruby_version.rb @@ -29,7 +29,7 @@ def support_target_ruby_version?(version) min = required_minimum_ruby_version || 0 max = required_maximum_ruby_version || Float::INFINITY - min <= version && max >= version + version.between?(min, max) end end end diff --git a/lib/rubocop/cop/style/class_and_module_children.rb b/lib/rubocop/cop/style/class_and_module_children.rb index 8654ca3f0b71..757bae92238f 100644 --- a/lib/rubocop/cop/style/class_and_module_children.rb +++ b/lib/rubocop/cop/style/class_and_module_children.rb @@ -3,14 +3,26 @@ module RuboCop module Cop module Style - # Checks the style of children definitions at classes and - # modules. Basically there are two different styles: + # Checks that namespaced classes and modules are defined with a consistent style. + # + # With `nested` style, classes and modules should be defined separately (one constant + # on each line, without `::`). With `compact` style, classes and modules should be + # defined with fully qualified names (using `::` for namespaces). + # + # NOTE: The style chosen will affect `Module.nesting` for the class or module. Using + # `nested` style will result in each level being added, whereas `compact` style will + # only include the fully qualified class or module name. + # + # By default, `EnforcedStyle` applies to both classes and modules. If desired, separate + # styles can be defined for classes and modules by using `EnforcedStyleForClasses` and + # `EnforcedStyleForModules` respectively. If not set, or set to nil, the `EnforcedStyle` + # value will be used. # # @safety # Autocorrection is unsafe. # - # Moving from compact to nested children requires knowledge of whether the - # outer parent is a module or a class. Moving from nested to compact requires + # Moving from `compact` to `nested` children requires knowledge of whether the + # outer parent is a module or a class. Moving from `nested` to `compact` requires # verification that the outer parent is defined elsewhere. RuboCop does not # have the knowledge to perform either operation safely and thus requires # manual oversight. @@ -42,16 +54,18 @@ class ClassAndModuleChildren < Base def on_class(node) return if node.parent_class && style != :nested - check_style(node, node.body) + check_style(node, node.body, style_for_classes) end def on_module(node) - check_style(node, node.body) + check_style(node, node.body, style_for_modules) end private def nest_or_compact(corrector, node) + style = node.class_type? ? style_for_classes : style_for_modules + if style == :nested nest_definition(corrector, node) else @@ -141,7 +155,7 @@ def leading_spaces(node) node.source_range.source_line[/\A\s*/] end - def check_style(node, body) + def check_style(node, body, style) return if node.identifier.namespace&.cbase_type? if style == :nested @@ -183,6 +197,14 @@ def needs_compacting?(body) def compact_node_name?(node) node.identifier.source.include?('::') end + + def style_for_classes + cop_config['EnforcedStyleForClasses'] || style + end + + def style_for_modules + cop_config['EnforcedStyleForModules'] || style + end end end end diff --git a/lib/rubocop/cop/style/commented_keyword.rb b/lib/rubocop/cop/style/commented_keyword.rb index d41702d10ce3..9666ce4a06b0 100644 --- a/lib/rubocop/cop/style/commented_keyword.rb +++ b/lib/rubocop/cop/style/commented_keyword.rb @@ -9,8 +9,8 @@ module Style # These keywords are: `class`, `module`, `def`, `begin`, `end`. # # Note that some comments - # (`:nodoc:`, `:yields:`, `rubocop:disable` and `rubocop:todo`) - # and RBS::Inline annotation comments are allowed. + # (`:nodoc:`, `:yields:`, `rubocop:disable` and `rubocop:todo`), + # RBS::Inline annotation, and Steep annotation (`steep:ignore`) are allowed. # # Autocorrection removes comments from `end` keyword and keeps comments # for `class`, `module`, `def` and `begin` above the keyword. @@ -60,6 +60,8 @@ class CommentedKeyword < Base SUBCLASS_DEFINITION = /\A\s*class\s+(\w|::)+\s*<\s*(\w|::)+/.freeze METHOD_DEFINITION = /\A\s*def\s/.freeze + STEEP_REGEXP = /#\ssteep:ignore(\s|\z)/.freeze + def on_new_investigation processed_source.comments.each do |comment| next unless offensive?(comment) && (match = source_line(comment).match(REGEXP)) @@ -86,6 +88,7 @@ def register_offense(comment, matched_keyword) def offensive?(comment) line = source_line(comment) return false if rbs_inline_annotation?(line, comment) + return false if steep_annotation?(comment) KEYWORD_REGEXES.any? { |r| r.match?(line) } && ALLOWED_COMMENT_REGEXES.none? { |r| r.match?(line) } @@ -105,6 +108,10 @@ def rbs_inline_annotation?(line, comment) false end end + + def steep_annotation?(comment) + comment.text.match?(STEEP_REGEXP) + end end end end diff --git a/lib/rubocop/cop/style/comparable_between.rb b/lib/rubocop/cop/style/comparable_between.rb new file mode 100644 index 000000000000..846f0976cfdc --- /dev/null +++ b/lib/rubocop/cop/style/comparable_between.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Style + # Checks for logical comparison which can be replaced with `Comparable#between?`. + # + # NOTE: `Comparable#between?` is on average slightly slower than logical comparison, + # although the difference generally isn't observable. If you require maximum + # performance, consider using logical comparison. + # + # @example + # + # # bad + # x >= min && x <= max + # + # # bad + # x <= max && x >= min + # + # # good + # x.between?(min, max) + # + class ComparableBetween < Base + extend AutoCorrector + + MSG = 'Prefer `%s` over logical comparison.' + + # @!method logical_comparison_between_by_min_first?(node) + def_node_matcher :logical_comparison_between_by_min_first?, <<~PATTERN + (and + (send + {$_value :>= $_min | $_min :<= $_value}) + (send + {$_value :<= $_max | $_max :>= $_value})) + PATTERN + + # @!method logical_comparison_between_by_max_first?(node) + def_node_matcher :logical_comparison_between_by_max_first?, <<~PATTERN + (and + (send + {$_value :<= $_max | $_max :>= $_value}) + (send + {$_value :>= $_min | $_min :<= $_value})) + PATTERN + + def on_and(node) + logical_comparison_between_by_min_first?(node) do |*args| + min_and_value, max_and_value = args.each_slice(2).to_a + + register_offense(node, min_and_value, max_and_value) + end + + logical_comparison_between_by_max_first?(node) do |*args| + max_and_value, min_and_value = args.each_slice(2).to_a + + register_offense(node, min_and_value, max_and_value) + end + end + + private + + def register_offense(node, min_and_value, max_and_value) + value = (min_and_value & max_and_value).first + min = min_and_value.find { _1 != value } + max = max_and_value.find { _1 != value } + + prefer = "#{value.source}.between?(#{min.source}, #{max.source})" + add_offense(node, message: format(MSG, prefer: prefer)) do |corrector| + corrector.replace(node, prefer) + end + end + end + end + end +end diff --git a/lib/rubocop/cop/style/double_negation.rb b/lib/rubocop/cop/style/double_negation.rb index 20ce65198a45..f261088d8c41 100644 --- a/lib/rubocop/cop/style/double_negation.rb +++ b/lib/rubocop/cop/style/double_negation.rb @@ -109,7 +109,7 @@ def find_def_node_from_ascendant(node) end def define_method?(node) - return false unless node.block_type? + return false unless node.any_block_type? child = node.child_nodes.first return false unless child.send_type? diff --git a/lib/rubocop/cop/style/exponential_notation.rb b/lib/rubocop/cop/style/exponential_notation.rb index 9a395a8ccc3f..41ad62f7e3ab 100644 --- a/lib/rubocop/cop/style/exponential_notation.rb +++ b/lib/rubocop/cop/style/exponential_notation.rb @@ -60,8 +60,8 @@ module Style class ExponentialNotation < Base include ConfigurableEnforcedStyle MESSAGES = { - scientific: 'Use a mantissa in [1, 10[.', - engineering: 'Use an exponent divisible by 3 and a mantissa in [0.1, 1000[.', + scientific: 'Use a mantissa >= 1 and < 10.', + engineering: 'Use an exponent divisible by 3 and a mantissa >= 0.1 and < 1000.', integral: 'Use an integer as mantissa, without trailing zero.' }.freeze diff --git a/lib/rubocop/cop/style/format_string_token.rb b/lib/rubocop/cop/style/format_string_token.rb index 6e2b11d2beda..63f9ba01ee06 100644 --- a/lib/rubocop/cop/style/format_string_token.rb +++ b/lib/rubocop/cop/style/format_string_token.rb @@ -3,16 +3,24 @@ module RuboCop module Cop module Style - # Use a consistent style for named format string tokens. + # Use a consistent style for tokens within a format string. # - # NOTE: `unannotated` style cop only works for strings - # which are passed as arguments to those methods: - # `printf`, `sprintf`, `format`, `%`. - # The reason is that _unannotated_ format is very similar - # to encoded URLs or Date/Time formatting strings. + # By default, all strings are evaluated. In some cases, this may be undesirable, + # as they could be used as arguments to a method that does not consider + # them to be tokens, but rather other identifiers or just part of the string. # - # This cop's allowed methods can be customized with `AllowedMethods`. - # By default, there are no allowed methods. + # `AllowedMethods` or `AllowedPatterns` can be configured with in order to mark specific + # methods as always allowed, thereby avoiding an offense from the cop. By default, there + # are no allowed methods. + # + # Additionally, the cop can be made conservative by configuring it with + # `Mode: conservative` (default `aggressive`). In this mode, tokens (regardless + # of `EnforcedStyle`) are only considered if used in the format string argument to the + # methods `printf`, `sprintf`, `format` and `%`. + # + # NOTE: Tokens in the `unannotated` style (eg. `%s`) are always treated as if + # configured with `Conservative: true`. This is done in order to prevent false positives, + # because this format is very similar to encoded URLs or Date/Time formatting strings. # # @example EnforcedStyle: annotated (default) # @@ -82,6 +90,20 @@ module Style # # good # redirect('foo/%{bar_id}') # + # @example Mode: conservative, EnforcedStyle: annotated + # # In `conservative` mode, offenses are only registered for strings + # # given to a known formatting method. + # + # # good + # "%{greeting}" + # foo("%{greeting}") + # + # # bad + # format("%{greeting}", greeting: 'Hello') + # printf("%{greeting}", greeting: 'Hello') + # sprintf("%{greeting}", greeting: 'Hello') + # "%{greeting}" % { greeting: 'Hello' } + # class FormatStringToken < Base include ConfigurableEnforcedStyle include AllowedMethods @@ -153,8 +175,9 @@ def autocorrect_sequence(corrector, detected_sequence, token_range) corrector.replace(token_range, correction) end - def unannotated_format?(node, detected_style) - detected_style == :unannotated && !format_string_in_typical_context?(node) + def allowed_string?(node, detected_style) + (detected_style == :unannotated || conservative?) && + !format_string_in_typical_context?(node) end def message(detected_style) @@ -203,7 +226,7 @@ def token_ranges(contents) def collect_detections(node) detections = [] tokens(node) do |detected_sequence, token_range| - unless unannotated_format?(node, detected_sequence.style) + unless allowed_string?(node, detected_sequence.style) detections << [detected_sequence, token_range] end end @@ -222,6 +245,10 @@ def allowed_unannotated?(detections) def max_unannotated_placeholders_allowed cop_config['MaxUnannotatedPlaceholdersAllowed'] end + + def conservative? + cop_config.fetch('Mode', :aggressive).to_sym == :conservative + end end end end diff --git a/lib/rubocop/cop/style/if_unless_modifier.rb b/lib/rubocop/cop/style/if_unless_modifier.rb index 1545b78e0e86..448039eba494 100644 --- a/lib/rubocop/cop/style/if_unless_modifier.rb +++ b/lib/rubocop/cop/style/if_unless_modifier.rb @@ -164,8 +164,8 @@ def too_long_due_to_modifier?(node) def too_long_due_to_comment_after_modifier?(node, comment) source_length = processed_source.lines[node.first_line - 1].length - source_length >= max_line_length && - source_length - comment.source_range.length <= max_line_length + + max_line_length.between?(source_length - comment.source_range.length, source_length) end def allowed_patterns diff --git a/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb b/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb index 47ce9df4d39b..62fe95ac7f9e 100644 --- a/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +++ b/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb @@ -108,7 +108,7 @@ def legitimate_call_with_parentheses?(node) # rubocop:disable Metrics/PerceivedC end def call_in_literals?(node) - parent = node.parent&.block_type? ? node.parent.parent : node.parent + parent = node.parent&.any_block_type? ? node.parent.parent : node.parent return false unless parent parent.type?(:pair, :array, :range) || @@ -117,7 +117,7 @@ def call_in_literals?(node) end def call_in_logical_operators?(node) - parent = node.parent&.block_type? ? node.parent.parent : node.parent + parent = node.parent&.any_block_type? ? node.parent.parent : node.parent return false unless parent logical_operator?(parent) || @@ -153,7 +153,7 @@ def call_with_braced_block?(node) end def call_in_argument_with_block?(node) - parent = node.parent&.block_type? && node.parent.parent + parent = node.parent&.any_block_type? && node.parent.parent return false unless parent parent.type?(:call, :super, :yield) diff --git a/lib/rubocop/cop/style/redundant_current_directory_in_path.rb b/lib/rubocop/cop/style/redundant_current_directory_in_path.rb index 7ee364bfae9c..38f36ce1ff74 100644 --- a/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +++ b/lib/rubocop/cop/style/redundant_current_directory_in_path.rb @@ -20,20 +20,30 @@ class RedundantCurrentDirectoryInPath < Base MSG = 'Remove the redundant current directory path.' RESTRICT_ON_SEND = %i[require_relative].freeze - CURRENT_DIRECTORY_PATH = './' + CURRENT_DIRECTORY_PREFIX = %r{./+}.freeze + REDUNDANT_CURRENT_DIRECTORY_PREFIX = /\A#{CURRENT_DIRECTORY_PREFIX}/.freeze def on_send(node) return unless (first_argument = node.first_argument) - return unless first_argument.str_content&.start_with?(CURRENT_DIRECTORY_PATH) - return unless (index = first_argument.source.index(CURRENT_DIRECTORY_PATH)) + return unless (index = first_argument.source.index(CURRENT_DIRECTORY_PREFIX)) + return unless (redundant_length = redundant_path_length(first_argument.str_content)) begin_pos = first_argument.source_range.begin.begin_pos + index - range = range_between(begin_pos, begin_pos + 2) + end_pos = begin_pos + redundant_length + range = range_between(begin_pos, end_pos) add_offense(range) do |corrector| corrector.remove(range) end end + + private + + def redundant_path_length(path) + return unless (match = path&.match(REDUNDANT_CURRENT_DIRECTORY_PREFIX)) + + match[0].length + end end end end diff --git a/lib/rubocop/cop/style/rescue_modifier.rb b/lib/rubocop/cop/style/rescue_modifier.rb index c64c28c97beb..2a586d990c4b 100644 --- a/lib/rubocop/cop/style/rescue_modifier.rb +++ b/lib/rubocop/cop/style/rescue_modifier.rb @@ -67,11 +67,13 @@ def parenthesized?(node) node.parent && parentheses?(node.parent) end + # rubocop:disable Metrics/AbcSize def correct_rescue_block(corrector, node, parenthesized) operation = node.body node_indentation, node_offset = indentation_and_offset(node, parenthesized) + corrector.wrap(operation, '[', ']') if operation.array_type? && !operation.bracketed? corrector.remove(range_between(operation.source_range.end_pos, node.source_range.end_pos)) corrector.insert_before(operation, "begin\n#{node_indentation}") corrector.insert_after(heredoc_end(operation) || operation, <<~RESCUE_CLAUSE.chop) @@ -81,6 +83,7 @@ def correct_rescue_block(corrector, node, parenthesized) #{node_offset}end RESCUE_CLAUSE end + # rubocop:enable Metrics/AbcSize def indentation_and_offset(node, parenthesized) node_indentation = indentation(node) diff --git a/lib/rubocop/directive_comment.rb b/lib/rubocop/directive_comment.rb index 96cc1fabae67..b35ed4a4292a 100644 --- a/lib/rubocop/directive_comment.rb +++ b/lib/rubocop/directive_comment.rb @@ -12,7 +12,7 @@ class DirectiveComment # @api private LINT_SYNTAX_COP = "#{LINT_DEPARTMENT}/Syntax" # @api private - COP_NAME_PATTERN = '([A-Z]\w+/)*(?:[A-Z]\w+)' + COP_NAME_PATTERN = '([A-Za-z]\w+/)*(?:[A-Za-z]\w+)' # @api private COP_NAMES_PATTERN = "(?:#{COP_NAME_PATTERN} , )*#{COP_NAME_PATTERN}" # @api private diff --git a/lib/rubocop/ext/regexp_node.rb b/lib/rubocop/ext/regexp_node.rb index 548f2a99abd2..84fb47c6f71e 100644 --- a/lib/rubocop/ext/regexp_node.rb +++ b/lib/rubocop/ext/regexp_node.rb @@ -43,7 +43,6 @@ def each_capture(named: ANY) def named_capturing?(exp, event, named) event == :enter && named == exp.respond_to?(:name) && - !exp.text.start_with?('(?<=') && exp.respond_to?(:capturing?) && exp.capturing? end diff --git a/lib/rubocop/version.rb b/lib/rubocop/version.rb index fa04e384ff0b..98e89aaeb980 100644 --- a/lib/rubocop/version.rb +++ b/lib/rubocop/version.rb @@ -3,7 +3,7 @@ module RuboCop # This module holds the RuboCop version information. module Version - STRING = '1.73.2' + STRING = '1.74.0' MSG = '%s (using %s, ' \ 'rubocop-ast %s, ' \ diff --git a/relnotes/v1.74.0.md b/relnotes/v1.74.0.md new file mode 100644 index 000000000000..304ad27bea38 --- /dev/null +++ b/relnotes/v1.74.0.md @@ -0,0 +1,33 @@ +### New features + +* [#13936](https://github.com/rubocop/rubocop/pull/13936): Adds new cop `Style/ComparableBetween`. ([@lovro-bikic][]) +* [#13943](https://github.com/rubocop/rubocop/pull/13943): Allow writing steep annotation to method definition for `Style/CommentedKeyword`. ([@dak2][]) + +### Bug fixes + +* [#13969](https://github.com/rubocop/rubocop/issues/13969): Fix a false positive for `Lint/SharedMutableDefault` when `capacity` keyword argument is used. ([@koic][]) +* [#13945](https://github.com/rubocop/rubocop/pull/13945): Fix a false positive for `Style/DoubleNegation` when calling `define_method`/`define_singleton_method` with a numblock. ([@earlopain][]) +* [#13971](https://github.com/rubocop/rubocop/pull/13971): Fix false alarm for config obsoletion. ([@koic][]) +* [#13960](https://github.com/rubocop/rubocop/pull/13960): Fix a false negative for `Lint/ReturnInVoidContext` when returning out of a block. ([@earlopain][]) +* [#13947](https://github.com/rubocop/rubocop/pull/13947): Fix a false negative for `Lint/UselessConstantScoping` for constants defined in `class << self`. ([@earlopain][]) +* [#13949](https://github.com/rubocop/rubocop/pull/13949): Fix a false negative for `Lint/NonLocalExitFromIterator` with numblocks. ([@earlopain][]) +* [#13975](https://github.com/rubocop/rubocop/issues/13975): Fix false positives for `Style/RedundantCurrentDirectoryInPath` when using a complex current directory path in `require_relative`. ([@koic][]) +* [#13963](https://github.com/rubocop/rubocop/issues/13963): Fix wrong autocorrect for `Lint/LiteralAsCondition` when the literal is followed by `return`, `break`, or `next`. ([@earlopain][]) +* [#13946](https://github.com/rubocop/rubocop/pull/13946): Fix some false positives for `Style/MethodCallWithArgsParentheses` with `EnforcedStyle: omit_parentheses` style and numblocks. ([@earlopain][]) +* [#13950](https://github.com/rubocop/rubocop/pull/13950): Fix sporadic errors about `rubocop-rails` or `rubocop-performance` extraction, even if they are already part of the Gemfile. ([@earlopain][]) +* [#13981](https://github.com/rubocop/rubocop/pull/13981): Prevent redundant plugin loading when a duplicate plugin is specified in an inherited config. ([@koic][]) +* [#13965](https://github.com/rubocop/rubocop/issues/13965): Update `Lint/RedundantCopDisableDirective` to register an offense when cop names are given with improper casing. ([@dvandersluis][]) +* [#13948](https://github.com/rubocop/rubocop/pull/13948): Fix wrong autocorrect for `Style/RescueModifier` when using parallel assignment and the right-hand-side is not a bracketed array. ([@earlopain][]) + +### Changes + +* [#12851](https://github.com/rubocop/rubocop/issues/12851): Add `EnforcedStyleForClasses` and `EnforcedStyleForModules` configuration options to `Style/ClassAndModuleChildren`. ([@dvandersluis][]) +* [#13979](https://github.com/rubocop/rubocop/pull/13979): Add `Mode: conservative` configuration to `Style/FormatStringToken` to make the cop only register offenses for strings given to `printf`, `sprintf`, `format`, and `%`. ([@dvandersluis][]) +* [#13977](https://github.com/rubocop/rubocop/issues/13977): Allow `TLS1_1` and `TLS1_2` by default in `Naming/VariableNumber` to accommodate OpenSSL version parameter names. ([@koic][]) +* [#13967](https://github.com/rubocop/rubocop/pull/13967): Make `Lint/RedundantTypeConversion` aware of redundant `to_d`. ([@koic][]) + +[@lovro-bikic]: https://github.com/lovro-bikic +[@dak2]: https://github.com/dak2 +[@koic]: https://github.com/koic +[@earlopain]: https://github.com/earlopain +[@dvandersluis]: https://github.com/dvandersluis diff --git a/spec/rubocop/config_loader_spec.rb b/spec/rubocop/config_loader_spec.rb index f299858c46ec..309739f7aa69 100644 --- a/spec/rubocop/config_loader_spec.rb +++ b/spec/rubocop/config_loader_spec.rb @@ -746,7 +746,9 @@ .to contain_exactly('foo.rb', 'test.rb') expect(examples_configuration['Include']).to contain_exactly('bar.rb', 'another_test.rb') expect(examples_configuration['AllowedIdentifiers']) - .to match_array(%w[capture3 iso8601 rfc1123_date rfc2822 rfc3339 rfc822 iso2 x86_64]) + .to match_array( + %w[TLS1_1 TLS1_2 capture3 iso8601 rfc1123_date rfc2822 rfc3339 rfc822 iso2 x86_64] + ) expect(examples_configuration['InheritedArraySpecifiedString']).to contain_exactly( 'bare string', 'string in array' diff --git a/spec/rubocop/config_obsoletion_spec.rb b/spec/rubocop/config_obsoletion_spec.rb index 5f99a7d8814d..35e003a8fabe 100644 --- a/spec/rubocop/config_obsoletion_spec.rb +++ b/spec/rubocop/config_obsoletion_spec.rb @@ -7,10 +7,11 @@ let(:configuration) { RuboCop::Config.new(hash, loaded_path) } let(:loaded_path) { 'example/.rubocop.yml' } + let(:plugins) { [] } let(:requires) { [] } before do - allow(configuration).to receive(:loaded_features).and_return(requires) + allow(configuration).to receive_messages(loaded_plugins: plugins, loaded_features: requires) described_class.files = [described_class::DEFAULT_RULES_FILE] end @@ -231,6 +232,32 @@ } end + context 'when the plugin extensions are loaded' do + let(:plugins) { %w[rubocop-rails rubocop-performance] } + + it 'does not print a warning message' do + expect { config_obsoletion.reject_obsolete! }.not_to raise_error + end + end + + context 'when only one plugin extension is loaded' do + let(:plugins) { %w[rubocop-performance] } + + let(:expected_message) do + <<~OUTPUT.chomp + `Rails` cops have been extracted to the `rubocop-rails` gem. + (obsolete configuration found in example/.rubocop.yml, please update it) + OUTPUT + end + + it 'prints a warning message' do + config_obsoletion.reject_obsolete! + raise 'Expected a RuboCop::ValidationError' + rescue RuboCop::ValidationError => e + expect(e.message).to eq(expected_message) + end + end + context 'when the extensions are loaded' do let(:requires) { %w[rubocop-rails rubocop-performance] } diff --git a/spec/rubocop/cop/lint/literal_as_condition_spec.rb b/spec/rubocop/cop/lint/literal_as_condition_spec.rb index e30da217241c..459487391c7b 100644 --- a/spec/rubocop/cop/lint/literal_as_condition_spec.rb +++ b/spec/rubocop/cop/lint/literal_as_condition_spec.rb @@ -549,4 +549,54 @@ end until false RUBY end + + context 'void value expressions after autocorrect' do + it 'registers an offense but does not autocorrect when `return` is used after `&&`' do + expect_offense(<<~RUBY) + def foo + puts 123 && return if bar? + ^^^ Literal `123` appeared as a condition. + end + RUBY + + expect_no_corrections + end + + it 'registers an offense but does not autocorrect when inside `if` and `return` is used after `&&`' do + expect_offense(<<~RUBY) + def foo + baz? if 123 && return + ^^^ Literal `123` appeared as a condition. + end + RUBY + + expect_no_corrections + end + + it 'registers an offense but does not autocorrect when `break` is used after `&&`' do + expect_offense(<<~RUBY) + def foo + bar do + puts 123 && break if baz? + ^^^ Literal `123` appeared as a condition. + end + end + RUBY + + expect_no_corrections + end + + it 'registers an offense but does not autocorrect when `next` is used after `&&`' do + expect_offense(<<~RUBY) + def foo + bar do + puts 123 && next if baz? + ^^^ Literal `123` appeared as a condition. + end + end + RUBY + + expect_no_corrections + end + end end diff --git a/spec/rubocop/cop/lint/non_local_exit_from_iterator_spec.rb b/spec/rubocop/cop/lint/non_local_exit_from_iterator_spec.rb index d0d5699da1a4..d9567a5c6240 100644 --- a/spec/rubocop/cop/lint/non_local_exit_from_iterator_spec.rb +++ b/spec/rubocop/cop/lint/non_local_exit_from_iterator_spec.rb @@ -12,6 +12,16 @@ end RUBY end + + it 'registers an offense for numblocks' do + expect_offense(<<~RUBY) + items.each do + return if baz?(_1) + ^^^^^^ Non-local exit from iterator, [...] + _1.update!(foobar: true) + end + RUBY + end end context 'and has multiple arguments' do diff --git a/spec/rubocop/cop/lint/redundant_cop_disable_directive_spec.rb b/spec/rubocop/cop/lint/redundant_cop_disable_directive_spec.rb index 378eca96242e..09ac763819ab 100644 --- a/spec/rubocop/cop/lint/redundant_cop_disable_directive_spec.rb +++ b/spec/rubocop/cop/lint/redundant_cop_disable_directive_spec.rb @@ -266,6 +266,28 @@ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{message} RUBY end + + context 'when the department starts with a lowercase letter' do + it 'registers an offense' do + expect_offense(<<~RUBY) + # rubocop:disable lint/SelfAssignment + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unnecessary disabling of `lint/SelfAssignment` (did you mean `Lint/SelfAssignment`?). + RUBY + + expect_correction('') + end + end + + context 'when the cop starts with a lowercase letter' do + it 'registers an offense' do + expect_offense(<<~RUBY) + # rubocop:disable Lint/selfAssignment + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unnecessary disabling of `Lint/selfAssignment` (did you mean `Lint/SelfAssignment`?). + RUBY + + expect_correction('') + end + end end context 'all cops' do diff --git a/spec/rubocop/cop/lint/redundant_type_conversion_spec.rb b/spec/rubocop/cop/lint/redundant_type_conversion_spec.rb index 5c1a948a0dc1..17d854c583b9 100644 --- a/spec/rubocop/cop/lint/redundant_type_conversion_spec.rb +++ b/spec/rubocop/cop/lint/redundant_type_conversion_spec.rb @@ -248,6 +248,18 @@ it_behaves_like 'accepted', 'Float("number", exception: false).to_f' end + describe '`to_d`' do + it_behaves_like 'conversion', :to_d + + it_behaves_like 'offense', :to_d, 'BigDecimal(42)' + it_behaves_like 'offense', :to_d, 'Kernel::BigDecimal(42)' + it_behaves_like 'offense', :to_d, '::Kernel::BigDecimal(42)' + it_behaves_like 'offense', :to_d, 'BigDecimal("number", exception: true)' + + it_behaves_like 'accepted', 'BigDecimal("number", exception: false).to_d' + it_behaves_like 'accepted', 'BigDecimal(obj, n, exception: false).to_d' + end + describe '`to_r`' do it_behaves_like 'conversion', :to_r diff --git a/spec/rubocop/cop/lint/return_in_void_context_spec.rb b/spec/rubocop/cop/lint/return_in_void_context_spec.rb index 7e655aed86b9..f4999895dc1b 100644 --- a/spec/rubocop/cop/lint/return_in_void_context_spec.rb +++ b/spec/rubocop/cop/lint/return_in_void_context_spec.rb @@ -12,6 +12,58 @@ def initialize end RUBY end + + it 'registers an offense when the value is returned in a block' do + expect_offense(<<~RUBY) + class A + def initialize + foo do + return :qux + ^^^^^^ Do not return a value in `initialize`. + end + end + end + RUBY + end + + it 'registers an offense when the value is returned in a numblock' do + expect_offense(<<~RUBY) + class A + def initialize + foo do + _1 + return :qux + ^^^^^^ Do not return a value in `initialize`. + end + end + end + RUBY + end + + it 'registers an offense when the value is returned from inside a proc' do + expect_offense(<<~RUBY) + class A + def initialize + proc do + return :qux + ^^^^^^ Do not return a value in `initialize`. + end + end + end + RUBY + end + + it 'registers no offense when the value is returned from inside a lamdba' do + expect_no_offenses(<<~RUBY) + class A + def initialize + lambda do + return :qux + end + end + end + RUBY + end end context 'with an initialize method containing a return without a value' do @@ -24,6 +76,18 @@ def initialize end RUBY end + + it 'accepts when the return is in a block' do + expect_no_offenses(<<~RUBY) + class A + def initialize + foo do + return if bar? + end + end + end + RUBY + end end context 'with a setter method containing a return with a value' do diff --git a/spec/rubocop/cop/lint/shared_mutable_default_spec.rb b/spec/rubocop/cop/lint/shared_mutable_default_spec.rb index ea7d80636a7e..b3b94a0b2ada 100644 --- a/spec/rubocop/cop/lint/shared_mutable_default_spec.rb +++ b/spec/rubocop/cop/lint/shared_mutable_default_spec.rb @@ -38,6 +38,14 @@ end end + context 'when `capacity` keyword argument is used' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + Hash.new(capacity: 42) + RUBY + end + end + context 'when Hash is initialized with an array' do it 'registers an offense' do expect_offense(<<~RUBY) @@ -59,4 +67,13 @@ RUBY end end + + context 'when Hash is initialized with a Hash and `capacity` keyword argument' do + it 'registers an offense' do + expect_offense(<<~RUBY) + Hash.new({}, capacity: 42) + ^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not create a Hash with a mutable default value [...] + RUBY + end + end end diff --git a/spec/rubocop/cop/lint/useless_constant_scoping_spec.rb b/spec/rubocop/cop/lint/useless_constant_scoping_spec.rb index 1d481bb2129b..a75d88abdfd8 100644 --- a/spec/rubocop/cop/lint/useless_constant_scoping_spec.rb +++ b/spec/rubocop/cop/lint/useless_constant_scoping_spec.rb @@ -54,12 +54,25 @@ class Foo RUBY end - it 'does not register an offense when using constant after `private` access modifier in `class << self`' do + it 'registers an offense when using constant after `private` access modifier in `class << self`' do + expect_offense(<<~RUBY) + class Foo + class << self + private + CONST = 42 + ^^^^^^^^^^ Useless `private` access modifier for constant scope. + end + end + RUBY + end + + it 'does not register an offense when using constant after `private` access modifier in `class << self` with `private_constant`' do expect_no_offenses(<<~RUBY) class Foo class << self private CONST = 42 + private_constant :CONST end end RUBY diff --git a/spec/rubocop/cop/style/class_and_module_children_spec.rb b/spec/rubocop/cop/style/class_and_module_children_spec.rb index 0b726ff6deb4..f8f0d77141e9 100644 --- a/spec/rubocop/cop/style/class_and_module_children_spec.rb +++ b/spec/rubocop/cop/style/class_and_module_children_spec.rb @@ -450,4 +450,96 @@ def foo; 1; end end end end + + context 'when EnforcedStyleForClasses is set' do + let(:cop_config) do + { 'EnforcedStyle' => 'nested', 'EnforcedStyleForClasses' => enforced_style_for_classes } + end + + context 'EnforcedStyleForClasses: nil' do + let(:enforced_style_for_classes) { nil } + + it 'uses the set `EnforcedStyle` for classes' do + expect_offense(<<~RUBY) + class FooClass::BarClass + ^^^^^^^^^^^^^^^^^^ Use nested module/class definitions instead of compact style. + end + RUBY + + expect_correction(<<~RUBY) + module FooClass + class BarClass + end + end + RUBY + end + end + + context 'EnforcedStyleForClasses: compact' do + let(:enforced_style_for_classes) { 'compact' } + + it 'registers an offense for classes with nested children' do + expect_offense(<<~RUBY) + class FooClass + ^^^^^^^^ Use compact module/class definition instead of nested style. + class BarClass + end + end + RUBY + + expect_correction(<<~RUBY) + class FooClass::BarClass + end + RUBY + end + end + end + + context 'when EnforcedStyleForModules is set' do + let(:cop_config) do + { 'EnforcedStyle' => 'nested', 'EnforcedStyleForModules' => enforced_style_for_modules } + end + + context 'EnforcedStyleForModules: nil' do + let(:enforced_style_for_modules) { nil } + + it 'uses the set `EnforcedStyle` for modules' do + expect_offense(<<~RUBY) + module FooModule::BarModule + ^^^^^^^^^^^^^^^^^^^^ Use nested module/class definitions instead of compact style. + end + RUBY + + expect_correction(<<~RUBY) + module FooModule + module BarModule + end + end + RUBY + end + end + + context 'EnforcedStyleForModules: compact' do + let(:enforced_style_for_modules) { 'compact' } + + it 'registers an offense for modules with nested children' do + expect_offense(<<~RUBY) + module FooModule + ^^^^^^^^^ Use compact module/class definition instead of nested style. + module BarModule + def method_example + end + end + end + RUBY + + expect_correction(<<~RUBY) + module FooModule::BarModule + def method_example + end + end + RUBY + end + end + end end diff --git a/spec/rubocop/cop/style/commented_keyword_spec.rb b/spec/rubocop/cop/style/commented_keyword_spec.rb index 5c02fcb5201b..9f61cc09b586 100644 --- a/spec/rubocop/cop/style/commented_keyword_spec.rb +++ b/spec/rubocop/cop/style/commented_keyword_spec.rb @@ -352,4 +352,203 @@ module Y end RUBY end + + context 'when Steep annotation is used' do + it 'does not register an offense for `steep:ignore` annotation on the same line as `def`' do + expect_no_offenses(<<~RUBY) + def x # steep:ignore + end + + def x # steep:ignore MethodBodyTypeMismatch + end + RUBY + end + + it 'does not register an offense for `steep:ignore` annotation on the same line as `end`' do + expect_no_offenses(<<~RUBY) + def x + end # steep:ignore + + def x + end # steep:ignore MethodBodyTypeMismatch + RUBY + end + + it 'does not register an offense for `steep:ignore` annotation on the same line as `begin`' do + expect_no_offenses(<<~RUBY) + begin # steep:ignore + end + + begin # steep:ignore NoMethod + end + RUBY + end + + it 'does not register an offense for `steep:ignore` annotation on the same line as `class`' do + expect_no_offenses(<<~RUBY) + class X # steep:ignore + end + + class X # steep:ignore UnknownConstant + end + RUBY + end + + it 'does not register an offense for `steep:ignore` annotation on the same line as `module`' do + expect_no_offenses(<<~RUBY) + module X # steep:ignore + end + + module X # steep:ignore UnknownConstant + end + RUBY + end + + it 'registers an offense and corrects for non `steep:ignore` annotation on the same line as `def' do + expect_offense(<<~RUBY) + def x # steep + ^^^^^^^ Do not place comments on the same line as the `def` keyword. + end + + def x #steep:ignore + ^^^^^^^^^^^^^ Do not place comments on the same line as the `def` keyword. + end + + def x # steep:ignoreMethodBodyTypeMismatch + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not place comments on the same line as the `def` keyword. + end + RUBY + + expect_correction(<<~RUBY) + # steep + def x + end + + #steep:ignore + def x + end + + # steep:ignoreMethodBodyTypeMismatch + def x + end + RUBY + end + + it 'registers an offense and corrects for non `steep:ignore` annotation on the same line as `end' do + expect_offense(<<~RUBY) + def x + end # steep + ^^^^^^^ Do not place comments on the same line as the `end` keyword. + + def x + end #steep:ignore + ^^^^^^^^^^^^^ Do not place comments on the same line as the `end` keyword. + + def x + end # steep:ignoreMethodBodyTypeMismatch + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not place comments on the same line as the `end` keyword. + RUBY + + expect_correction(<<~RUBY) + def x + end + + def x + end + + def x + end + RUBY + end + + it 'registers an offense and corrects for non `steep:ignore` annotation on the same line as `begin' do + expect_offense(<<~RUBY) + begin # steep + ^^^^^^^ Do not place comments on the same line as the `begin` keyword. + end + + begin #steep:ignore + ^^^^^^^^^^^^^ Do not place comments on the same line as the `begin` keyword. + end + + begin # steep:ignoreUnknownConstant + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not place comments on the same line as the `begin` keyword. + end + RUBY + + expect_correction(<<~RUBY) + # steep + begin + end + + #steep:ignore + begin + end + + # steep:ignoreUnknownConstant + begin + end + RUBY + end + + it 'registers an offense and corrects for non `steep:ignore` annotation on the same line as `class' do + expect_offense(<<~RUBY) + class X # steep + ^^^^^^^ Do not place comments on the same line as the `class` keyword. + end + + class X #steep:ignore + ^^^^^^^^^^^^^ Do not place comments on the same line as the `class` keyword. + end + + class X # steep:ignoreUnknownConstant + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not place comments on the same line as the `class` keyword. + end + RUBY + + expect_correction(<<~RUBY) + # steep + class X + end + + #steep:ignore + class X + end + + # steep:ignoreUnknownConstant + class X + end + RUBY + end + + it 'registers an offense and corrects for non `steep:ignore` annotation on the same line as `module' do + expect_offense(<<~RUBY) + module X # steep + ^^^^^^^ Do not place comments on the same line as the `module` keyword. + end + + module X #steep:ignore + ^^^^^^^^^^^^^ Do not place comments on the same line as the `module` keyword. + end + + module X # steep:ignoreUnknownConstant + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not place comments on the same line as the `module` keyword. + end + RUBY + + expect_correction(<<~RUBY) + # steep + module X + end + + #steep:ignore + module X + end + + # steep:ignoreUnknownConstant + module X + end + RUBY + end + end end diff --git a/spec/rubocop/cop/style/comparable_between_spec.rb b/spec/rubocop/cop/style/comparable_between_spec.rb new file mode 100644 index 000000000000..72277aef23aa --- /dev/null +++ b/spec/rubocop/cop/style/comparable_between_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Style::ComparableBetween, :config do + [ + 'x >= min && x <= max', + 'x >= min && max >= x', + 'min <= x && x <= max', + 'min <= x && max >= x', + 'x <= max && x >= min', + 'x <= max && min <= x', + 'max >= x && x >= min', + 'max >= x && min <= x' + ].each do |logical_comparison| + it "registers an offense with logical comparison #{logical_comparison}" do + expect_offense(<<~RUBY) + #{logical_comparison} + ^^^^^^^^^^^^^^^^^^^^ Prefer `x.between?(min, max)` over logical comparison. + RUBY + + expect_correction(<<~RUBY) + x.between?(min, max) + RUBY + end + end + + it 'registers an offense when using logical comparison with `and`' do + expect_offense(<<~RUBY) + x >= min and x <= max + ^^^^^^^^^^^^^^^^^^^^^ Prefer `x.between?(min, max)` over logical comparison. + RUBY + + expect_correction(<<~RUBY) + x.between?(min, max) + RUBY + end + + it 'does not register an offense when logical comparison excludes max value' do + expect_no_offenses(<<~RUBY) + x >= min && x < max + RUBY + end + + it 'does not register an offense when logical comparison excludes min value' do + expect_no_offenses(<<~RUBY) + x > min && x <= max + RUBY + end + + it 'does not register an offense when logical comparison excludes min and max value' do + expect_no_offenses(<<~RUBY) + x > min && x < max + RUBY + end + + it 'does not register an offense when logical comparison has different subjects for min and max' do + expect_no_offenses(<<~RUBY) + x >= min && y <= max + RUBY + end +end diff --git a/spec/rubocop/cop/style/double_negation_spec.rb b/spec/rubocop/cop/style/double_negation_spec.rb index 96920eb3c9bd..f01aa34fd5a7 100644 --- a/spec/rubocop/cop/style/double_negation_spec.rb +++ b/spec/rubocop/cop/style/double_negation_spec.rb @@ -502,6 +502,15 @@ def foo? RUBY end + it 'does not register an offense for `!!` when return location by `define_method` with numblock' do + expect_no_offenses(<<~RUBY) + define_method :foo? do + do_something(_1) + !!qux + end + RUBY + end + it 'does not register an offense for `!!` when return location by `define_singleton_method`' do expect_no_offenses(<<~RUBY) define_singleton_method :foo? do diff --git a/spec/rubocop/cop/style/exponential_notation_spec.rb b/spec/rubocop/cop/style/exponential_notation_spec.rb index 3787f87a551e..a3ddb231c346 100644 --- a/spec/rubocop/cop/style/exponential_notation_spec.rb +++ b/spec/rubocop/cop/style/exponential_notation_spec.rb @@ -7,21 +7,21 @@ it 'registers an offense for mantissa equal to 10' do expect_offense(<<~RUBY) 10e6 - ^^^^ Use a mantissa in [1, 10[. + ^^^^ Use a mantissa >= 1 and < 10. RUBY end it 'registers an offense for mantissa greater than 10' do expect_offense(<<~RUBY) 12.34e3 - ^^^^^^^ Use a mantissa in [1, 10[. + ^^^^^^^ Use a mantissa >= 1 and < 10. RUBY end it 'registers an offense for mantissa smaller than 1' do expect_offense(<<~RUBY) 0.314e1 - ^^^^^^^ Use a mantissa in [1, 10[. + ^^^^^^^ Use a mantissa >= 1 and < 10. RUBY end @@ -56,35 +56,35 @@ it 'registers an offense for exponent equal to 4' do expect_offense(<<~RUBY) 10e4 - ^^^^ Use an exponent divisible by 3 and a mantissa in [0.1, 1000[. + ^^^^ Use an exponent divisible by 3 and a mantissa >= 0.1 and < 1000. RUBY end it 'registers an offense for exponent equal to -2' do expect_offense(<<~RUBY) 12.3e-2 - ^^^^^^^ Use an exponent divisible by 3 and a mantissa in [0.1, 1000[. + ^^^^^^^ Use an exponent divisible by 3 and a mantissa >= 0.1 and < 1000. RUBY end it 'registers an offense for mantissa smaller than 0.1' do expect_offense(<<~RUBY) 0.09e9 - ^^^^^^ Use an exponent divisible by 3 and a mantissa in [0.1, 1000[. + ^^^^^^ Use an exponent divisible by 3 and a mantissa >= 0.1 and < 1000. RUBY end it 'registers an offense for a mantissa greater than -0.1' do expect_offense(<<~RUBY) -0.09e3 - ^^^^^^^ Use an exponent divisible by 3 and a mantissa in [0.1, 1000[. + ^^^^^^^ Use an exponent divisible by 3 and a mantissa >= 0.1 and < 1000. RUBY end it 'registers an offense for mantissa smaller than -1000' do expect_offense(<<~RUBY) -1012.34e6 - ^^^^^^^^^^ Use an exponent divisible by 3 and a mantissa in [0.1, 1000[. + ^^^^^^^^^^ Use an exponent divisible by 3 and a mantissa >= 0.1 and < 1000. RUBY end diff --git a/spec/rubocop/cop/style/format_string_token_spec.rb b/spec/rubocop/cop/style/format_string_token_spec.rb index e10fd6700c7a..b45d0cadaa7f 100644 --- a/spec/rubocop/cop/style/format_string_token_spec.rb +++ b/spec/rubocop/cop/style/format_string_token_spec.rb @@ -4,6 +4,7 @@ let(:enforced_style) { :annotated } let(:allowed_methods) { [] } let(:allowed_patterns) { [] } + let(:mode) { :aggressive } let(:cop_config) do { @@ -11,7 +12,8 @@ 'SupportedStyles' => %i[annotated template unannotated], 'MaxUnannotatedPlaceholdersAllowed' => 0, 'AllowedMethods' => allowed_methods, - 'AllowedPatterns' => allowed_patterns + 'AllowedPatterns' => allowed_patterns, + 'Mode' => mode } end @@ -416,4 +418,60 @@ expect_no_corrections end end + + context 'with `Mode: conservative`' do + let(:mode) { :conservative } + + %i[annotated template unannotated].each do |style| + context "when enforced style is #{style}" do + let(:enforced_style) { style } + + shared_examples 'conservative' do |given_style, string| + context "with `#{string}`" do + context 'with a bare string' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + '#{string}' + RUBY + end + end + + if style != given_style + %i[printf sprintf format].each do |method| + context "as an argument to `#{method}`" do + it 'registers an offense' do + expect_offense(<<~RUBY, string: string, method: method) + %{method}('%{string}', *vars) + _{method} ^{string} Prefer [...] + RUBY + end + end + end + + context 'as an argument to `%`' do + it 'registers an offense' do + expect_offense(<<~RUBY, string: string) + '#{string}' % vars + ^{string} Prefer [...] + RUBY + end + end + end + + context 'as an argument to another method' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + foo('#{string}') + RUBY + end + end + end + end + + it_behaves_like 'conservative', :annotated, '%s' + it_behaves_like 'conservative', :template, '%{greetings}' + it_behaves_like 'conservative', :unannotated, '%s' + end + end + end end diff --git a/spec/rubocop/cop/style/method_call_with_args_parentheses_spec.rb b/spec/rubocop/cop/style/method_call_with_args_parentheses_spec.rb index af8aa734713f..bfd0fe0ee4e3 100644 --- a/spec/rubocop/cop/style/method_call_with_args_parentheses_spec.rb +++ b/spec/rubocop/cop/style/method_call_with_args_parentheses_spec.rb @@ -839,6 +839,16 @@ def seatle_style arg: default(42) RUBY end + it 'accepts parens in array literal calls with numblocks' do + expect_no_offenses(<<~RUBY) + [ + foo.bar.quux(:args) do + pass _1 + end, + ] + RUBY + end + it 'accepts parens in calls with logical operators' do expect_no_offenses('foo(a) && bar(b)') expect_no_offenses('foo(a) || bar(b)') @@ -849,6 +859,14 @@ def seatle_style arg: default(42) RUBY end + it 'accepts parens in calls with logical operator and numblock' do + expect_no_offenses(<<~RUBY) + foo(a) || bar(b) do + pass _1 + end + RUBY + end + it 'accepts parens in calls with args with logical operators' do expect_no_offenses('foo(a, b || c)') expect_no_offenses('foo a, b || c') @@ -1157,6 +1175,16 @@ def seatle_style arg: default(42) RUBY end + it 'accepts parens in argument calls with numblocks' do + expect_no_offenses(<<~RUBY) + foo( + bar.new(quux) do + pass _1 + end + ) + RUBY + end + it 'accepts parens in argument csend with blocks' do expect_no_offenses(<<~RUBY) foo( diff --git a/spec/rubocop/cop/style/redundant_current_directory_in_path_spec.rb b/spec/rubocop/cop/style/redundant_current_directory_in_path_spec.rb index a3c5dd21e16d..82c0a7bd0db9 100644 --- a/spec/rubocop/cop/style/redundant_current_directory_in_path_spec.rb +++ b/spec/rubocop/cop/style/redundant_current_directory_in_path_spec.rb @@ -23,6 +23,17 @@ RUBY end + it "registers an offense when using a complex current directory path in `require_relative '...'`" do + expect_offense(<<~RUBY) + require_relative './//./../path/to/feature' + ^^^^ Remove the redundant current directory path. + RUBY + + expect_correction(<<~RUBY) + require_relative '../path/to/feature' + RUBY + end + it 'registers an offense when using a current directory path in `require_relative %q(...)`' do expect_offense(<<~RUBY) require_relative %q(./path/to/feature) @@ -40,6 +51,12 @@ RUBY end + it 'does not register an offense when using a path that starts with a dot in `require_relative`' do + expect_no_offenses(<<~RUBY) + require_relative '.path' + RUBY + end + it 'does not register an offense when not using a current directory path in `require_relative`' do expect_no_offenses(<<~RUBY) require_relative 'path/to/feature' diff --git a/spec/rubocop/cop/style/rescue_modifier_spec.rb b/spec/rubocop/cop/style/rescue_modifier_spec.rb index 965413846e55..d5cd652292fb 100644 --- a/spec/rubocop/cop/style/rescue_modifier_spec.rb +++ b/spec/rubocop/cop/style/rescue_modifier_spec.rb @@ -103,6 +103,14 @@ a, b = 1, 2 rescue nil ^^^^^^^^^^^^^^^^^^^^^^ Avoid using `rescue` in its modifier form. RUBY + + expect_correction(<<~RUBY) + begin + a, b = 1, 2 + rescue + nil + end + RUBY end it 'registers an offense for modifier rescue around parallel assignment', :ruby27 do @@ -110,6 +118,14 @@ a, b = 1, 2 rescue nil ^^^^^^^^^^^^^^^ Avoid using `rescue` in its modifier form. RUBY + + expect_correction(<<~RUBY) + a, b = begin + [1, 2] + rescue + nil + end + RUBY end it 'handles more complex expression with modifier rescue' do diff --git a/spec/rubocop/version_spec.rb b/spec/rubocop/version_spec.rb index cf49cc007797..bfae007239e0 100644 --- a/spec/rubocop/version_spec.rb +++ b/spec/rubocop/version_spec.rb @@ -104,6 +104,48 @@ module FooBarBaz end end + context 'when plugins are specified' do + before do + create_file('.rubocop.yml', <<~YAML) + plugins: + - rubocop-performance + - rubocop-rspec + YAML + end + + it 'returns the extensions' do + expect(extension_versions).to contain_exactly( + /- rubocop-performance \d+\.\d+\.\d+/, + /- rubocop-rspec \d+\.\d+\.\d+/ + ) + end + end + + context 'when a duplicate plugin is specified in an inherited config' do + before do + create_file('base.yml', <<~YAML) + plugins: + - rubocop-performance + YAML + + create_file('.rubocop.yml', <<~YAML) + inherit_from: + - base.yml + + plugins: + - rubocop-performance + - rubocop-rspec + YAML + end + + it 'returns each extension exactly once' do + expect(extension_versions).to contain_exactly( + /- rubocop-performance \d+\.\d+\.\d+/, + /- rubocop-rspec \d+\.\d+\.\d+/ + ) + end + end + context 'with an invalid cop in config' do before do create_file('.rubocop.yml', <<~YAML)