diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 347e71915a59..ceeae965c973 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.77.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] +1.78.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/CHANGELOG.md b/CHANGELOG.md index fca8834e7a41..4190fae6b95e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,31 @@ ## master (unreleased) +## 1.78.0 (2025-07-08) + +### New features + +* [#14331](https://github.com/rubocop/rubocop/pull/14331): Enhance `Naming/MethodName` cop to detect offenses within `define_method` calls. ([@viralpraxis][]) +* [#14325](https://github.com/rubocop/rubocop/pull/14325): Enhance `Naming/MethodName` cop to handle offenses within `Struct` members. ([@viralpraxis][]) +* [#14335](https://github.com/rubocop/rubocop/pull/14335): Enhance `Security/Eval` cop to detect `Kernel.eval` calls. ([@viralpraxis][]) + +### Bug fixes + +* [#14343](https://github.com/rubocop/rubocop/pull/14343): Fix autocorrect code for `Style/HashConversion` to avoid syntax error. ([@koic][]) +* [#14346](https://github.com/rubocop/rubocop/issues/14346): Avoid requiring parentheses for `Style/SingleLineMethods`. ([@koic][]) +* [#14339](https://github.com/rubocop/rubocop/pull/14339): Fix bug where specifying `--format` disables parallelization. ([@r7kamura][]) +* [#14300](https://github.com/rubocop/rubocop/pull/14300): Fix false positives for `Lint/DuplicateMethods` cop when self-alias trick is used. ([@viralpraxis][]) +* [#14329](https://github.com/rubocop/rubocop/issues/14329): Fix false positives for `Lint/LiteralAsCondition` when a literal is used inside `||` in `case` condition. ([@koic][]) +* [#14326](https://github.com/rubocop/rubocop/issues/14326): Fix additional autocorrection errors in `Style/HashConversion` for nested `Hash[]` calls. ([@dvandersluis][]) +* [#14031](https://github.com/rubocop/rubocop/issues/14031): Honor --config options on server mode. ([@steiley][]) +* [#14319](https://github.com/rubocop/rubocop/pull/14319): Fix the following incorrect autocorrect for `Lint/RedundantTypeConversion` when using parentheses with no arguments or any arguments. ([@koic][]) +* [#14336](https://github.com/rubocop/rubocop/issues/14336): Fix incorrect autocorrect for `Style/ItBlockParameter` when using a single numbered parameter after multiple numbered parameters in a method chain. ([@koic][]) +* [#11782](https://github.com/rubocop/rubocop/issues/11782): Move pending cops warning out of ConfigLoader. ([@nobuyo][]) + +### Changes + +* [#14318](https://github.com/rubocop/rubocop/issues/14318): Add `WaywardPredicates` config to `Naming/PredicateMethod` to handle methods that look like predicates but aren't. ([@dvandersluis][]) + ## 1.77.0 (2025-06-20) ### New features diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9b7e48e19ff8..93f60a101e42 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,7 @@ do so. ```console $ rubocop -V -1.77.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] +1.78.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 cb3b0ddc524b..e21417373a53 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi in your `Gemfile`: ```rb -gem 'rubocop', '~> 1.77', require: false +gem 'rubocop', '~> 1.78', 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 c890f64dbc89..6cbe6e4d1e2d 100644 --- a/config/default.yml +++ b/config/default.yml @@ -3072,7 +3072,7 @@ Naming/PredicateMethod: Description: 'Checks that predicate methods end with `?` and non-predicate methods do not.' Enabled: pending VersionAdded: '1.76' - VersionChanged: '1.76' + VersionChanged: '1.78' # In `aggressive` mode, the cop will register an offense for predicate methods that # may return a non-boolean value. # In `conservative` mode, the cop will *not* register an offense for predicate methods @@ -3082,6 +3082,9 @@ Naming/PredicateMethod: - call AllowedPatterns: [] AllowBangMethods: false + # Methods that are known to not return a boolean value, despite ending in `?`. + WaywardPredicates: + - nonzero? Naming/PredicatePrefix: Description: 'Predicate method names should not be prefixed and end with a `?`.' diff --git a/docs/antora.yml b/docs/antora.yml index 4d9d768d7d22..376ed1cd898a 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.77' +version: '1.78' nav: - modules/ROOT/nav.adoc diff --git a/docs/modules/ROOT/pages/cops_lint.adoc b/docs/modules/ROOT/pages/cops_lint.adoc index a6e615328aa1..310e984fec1c 100644 --- a/docs/modules/ROOT/pages/cops_lint.adoc +++ b/docs/modules/ROOT/pages/cops_lint.adoc @@ -1747,6 +1747,10 @@ end Checks for duplicated instance (or singleton) method definitions. +NOTE: Aliasing a method to itself is allowed, as it indicates that +the developer intends to suppress Ruby's method redefinition warnings. +See https://bugs.ruby-lang.org/issues/13574. + [#examples-lintduplicatemethods] === Examples @@ -1783,6 +1787,18 @@ def foo end alias bar foo + +# good +alias foo foo +def foo + 1 +end + +# good +alias_method :foo, :foo +def foo + 1 +end ---- [#allcops_activesupportextensionsenabled_-false-_default_-lintduplicatemethods] diff --git a/docs/modules/ROOT/pages/cops_naming.adoc b/docs/modules/ROOT/pages/cops_naming.adoc index f702d29887b4..d4fcc9cbeda4 100644 --- a/docs/modules/ROOT/pages/cops_naming.adoc +++ b/docs/modules/ROOT/pages/cops_naming.adoc @@ -1052,6 +1052,20 @@ def fooBar; end # good def foo_bar; end + +# bad +define_method :fooBar do +end + +# good +define_method :foo_bar do +end + +# bad +Struct.new(:fooBar) + +# good +Struct.new(:foo_bar) ---- [#enforcedstyle_-camelcase-namingmethodname] @@ -1064,6 +1078,20 @@ def foo_bar; end # good def fooBar; end + +# bad +define_method :foo_bar do +end + +# good +define_method :fooBar do +end + +# bad +Struct.new(:foo_bar) + +# good +Struct.new(:fooBar) ---- [#forbiddenidentifiers_-__def__-_super__-namingmethodname] @@ -1206,7 +1234,7 @@ end | Yes | No | 1.76 -| 1.76 +| 1.78 |=== Checks that predicate methods end with `?` and non-predicate methods do not. @@ -1232,6 +1260,11 @@ registering an offense from a method name that does not confirm to the naming guidelines. By default, `call` is allowed. The cop also has `AllowedPatterns` configuration to allow method names by regular expression. +Although returning a call to another predicate method is treated as a boolean value, +certain method names can be known to not return a boolean, despite ending in a `?` +(for example, `Numeric#nonzero?` returns `self` or `nil`). These methods can be +configured using `NonBooleanPredicates`. + The cop can furthermore be configured to allow all bang methods (method names ending with `!`), with `AllowBangMethods: true` (default false). @@ -1370,6 +1403,10 @@ end | AllowBangMethods | `false` | Boolean + +| WaywardPredicates +| `nonzero?` +| Array |=== [#namingpredicateprefix] diff --git a/docs/modules/ROOT/pages/cops_security.adoc b/docs/modules/ROOT/pages/cops_security.adoc index 9c37bceb4784..3440aa9735d1 100644 --- a/docs/modules/ROOT/pages/cops_security.adoc +++ b/docs/modules/ROOT/pages/cops_security.adoc @@ -74,6 +74,7 @@ Checks for the use of `Kernel#eval` and `Binding#eval`. eval(something) binding.eval(something) +Kernel.eval(something) ---- [#securityiomethods] @@ -252,6 +253,7 @@ URI.parse(something).open # good (literal strings) open("foo.text") URI.open("http://example.com") +URI.parse(url).open ---- [#securityyamlload] diff --git a/docs/modules/ROOT/pages/cops_style.adoc b/docs/modules/ROOT/pages/cops_style.adoc index 3bdcd3e183e7..433c511106be 100644 --- a/docs/modules/ROOT/pages/cops_style.adoc +++ b/docs/modules/ROOT/pages/cops_style.adoc @@ -9262,6 +9262,30 @@ class Foo end ---- +[#allowedmethods_-__puts__-_print__-stylemethodcallwithargsparentheses] +==== AllowedMethods: ["puts", "print"] + +[source,ruby] +---- +# good +puts "Hello world" +print "Hello world" +# still enforces parentheses on other methods +array.delete(e) +---- + +[#allowedpatterns_-___assert__-stylemethodcallwithargsparentheses] +==== AllowedPatterns: ["^assert"] + +[source,ruby] +---- +# good +assert_equal 'test', x +assert_match(/foo/, bar) +# still enforces parentheses on other methods +array.delete(e) +---- + [#allowparenthesesinmultilinecall_-false-_default_-stylemethodcallwithargsparentheses] ==== AllowParenthesesInMultilineCall: false (default) diff --git a/docs/modules/ROOT/pages/development.adoc b/docs/modules/ROOT/pages/development.adoc index 1fd935dce125..766bceefcb7b 100644 --- a/docs/modules/ROOT/pages/development.adoc +++ b/docs/modules/ROOT/pages/development.adoc @@ -381,9 +381,9 @@ This can be implemented using the `IgnoredNode` module: end ---- -This works because the correcting a file is implemented by repeating investigation and correction until the file no longer requires correction, meaning all nested nodes will eventually be processed. +This works because file correction is implemented by repeating investigation and correction until the file no longer requires correction, meaning all nested nodes will eventually be processed. -Note that `expect_correction` in `Cop` specs only asserts the result after one pass. +Note that `expect_correction` in `Cop` specs asserts the result after all passes. === Limit by Ruby or Gem versions @@ -446,7 +446,7 @@ class MyCop < Base requires_gem "my-gem" def on_send(node) - if target_gem_version("my-gem) < "2.0" + if target_gem_version("my-gem") < "2.0" # ... else # ... diff --git a/docs/modules/ROOT/pages/installation.adoc b/docs/modules/ROOT/pages/installation.adoc index 7bc97b331abd..a290c57849b2 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.77', require: false +gem 'rubocop', '~> 1.78', 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 b90e36e0345c..f98a9c49fa7a 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.77.0 + rev: v1.78.0 hooks: - id: rubocop ---- @@ -136,7 +136,7 @@ entries in `additional_dependencies`: [source,yaml] ---- - repo: https://github.com/rubocop/rubocop - rev: v1.77.0 + rev: v1.78.0 hooks: - id: rubocop additional_dependencies: diff --git a/lib/rubocop.rb b/lib/rubocop.rb index 3b28977d3a19..e61a01902a7f 100644 --- a/lib/rubocop.rb +++ b/lib/rubocop.rb @@ -812,6 +812,7 @@ require_relative 'rubocop/remote_config' require_relative 'rubocop/target_ruby' require_relative 'rubocop/yaml_duplication_checker' +require_relative 'rubocop/pending_cops_reporter' # rubocop:enable Style/RequireOrder diff --git a/lib/rubocop/cli.rb b/lib/rubocop/cli.rb index ba4970789123..f4a34c1928da 100644 --- a/lib/rubocop/cli.rb +++ b/lib/rubocop/cli.rb @@ -12,7 +12,7 @@ class CLI STATUS_INTERRUPTED = Signal.list['INT'] + 128 DEFAULT_PARALLEL_OPTIONS = %i[ color config debug display_style_guide display_time display_only_fail_level_offenses - display_only_failed editor_mode except extra_details fail_level fix_layout format + display_only_failed editor_mode except extra_details fail_level fix_layout format formatters ignore_disable_comments lint only only_guide_cops require safe autocorrect safe_autocorrect autocorrect_all ].freeze @@ -48,6 +48,7 @@ def run(args = ARGV) validate_options_vs_config parallel_by_default! apply_default_formatter + report_pending_cops execute_runners end end @@ -155,6 +156,7 @@ def parallel_by_default! def act_on_options set_options_to_config_loader + set_options_to_pending_cops_reporter handle_editor_mode @config_store.options_config = @options[:config] if @options[:config] @@ -179,6 +181,11 @@ def set_options_to_config_loader ConfigLoader.ignore_unrecognized_cops = @options[:ignore_unrecognized_cops] end + def set_options_to_pending_cops_reporter + PendingCopsReporter.disable_pending_cops = @options[:disable_pending_cops] + PendingCopsReporter.enable_pending_cops = @options[:enable_pending_cops] + end + def handle_editor_mode RuboCop::LSP.enable if @options[:editor_mode] end @@ -208,5 +215,9 @@ def apply_default_formatter [[formatter, @options[:output_path]]] end end + + def report_pending_cops + PendingCopsReporter.warn_if_needed(@config_store.for_pwd) + end end end diff --git a/lib/rubocop/config_loader.rb b/lib/rubocop/config_loader.rb index 6f5347b89b6b..eeeb787b40bc 100644 --- a/lib/rubocop/config_loader.rb +++ b/lib/rubocop/config_loader.rb @@ -22,14 +22,6 @@ class ConfigLoader class << self include FileFinder - PENDING_BANNER = <<~BANNER - The following cops were added to RuboCop, but are not configured. Please set Enabled to either `true` or `false` in your `.rubocop.yml` file. - - Please also note that you can opt-in to new cops by default by adding this to your config: - AllCops: - NewCops: enable - BANNER - attr_accessor :debug, :ignore_parent_exclusion, :disable_pending_cops, :enable_pending_cops, :ignore_unrecognized_cops attr_writer :default_configuration @@ -132,21 +124,7 @@ def configuration_from_file(config_file, check: true) add_excludes_from_files(config, config_file) end - merge_with_default(config, config_file).tap do |merged_config| - unless possible_new_cops?(merged_config) - pending_cops = pending_cops_only_qualified(merged_config.pending_cops) - warn_on_pending_cops(pending_cops) unless pending_cops.empty? - end - end - end - - def pending_cops_only_qualified(pending_cops) - pending_cops.select { |cop| Cop::Registry.qualified_cop?(cop.name) } - end - - def possible_new_cops?(config) - disable_pending_cops || enable_pending_cops || - config.disabled_new_cops? || config.enabled_new_cops? + merge_with_default(config, config_file) end def add_excludes_from_files(config, config_file) @@ -208,21 +186,6 @@ def project_root ConfigFinder.project_root end - def warn_on_pending_cops(pending_cops) - warn Rainbow(PENDING_BANNER).yellow - - pending_cops.each { |cop| warn_pending_cop cop } - - warn Rainbow('For more information: https://docs.rubocop.org/rubocop/versioning.html').yellow - end - - def warn_pending_cop(cop) - version = cop.metadata['VersionAdded'] || 'N/A' - - warn Rainbow("#{cop.name}: # new in #{version}").yellow - warn Rainbow(' Enabled: true').yellow - end - # Merges the given configuration with the default one. def merge_with_default(config, config_file, unset_nil: true) resolver.merge_with_default(config, config_file, unset_nil: unset_nil) diff --git a/lib/rubocop/cop/internal_affairs/example_description.rb b/lib/rubocop/cop/internal_affairs/example_description.rb index 36e8dac73ef7..180f52f64c7a 100644 --- a/lib/rubocop/cop/internal_affairs/example_description.rb +++ b/lib/rubocop/cop/internal_affairs/example_description.rb @@ -46,7 +46,7 @@ class ExampleDescription < Base /\A(does not|doesn't) (register|find|flag|report)/ => 'registers', /\A(does not|doesn't) add (a|an|any )?offense/ => 'registers an offense', /\Aregisters no offense/ => 'registers an offense', - /\A(accepts|register)\b/ => 'registers' + /\A(accepts|allows|register)\b/ => 'registers' }.freeze EXPECT_NO_CORRECTIONS_DESCRIPTION_MAPPING = { diff --git a/lib/rubocop/cop/lint/duplicate_methods.rb b/lib/rubocop/cop/lint/duplicate_methods.rb index 0670cfede338..90a3d6482bd6 100644 --- a/lib/rubocop/cop/lint/duplicate_methods.rb +++ b/lib/rubocop/cop/lint/duplicate_methods.rb @@ -6,6 +6,10 @@ module Lint # Checks for duplicated instance (or singleton) method # definitions. # + # NOTE: Aliasing a method to itself is allowed, as it indicates that + # the developer intends to suppress Ruby's method redefinition warnings. + # See https://bugs.ruby-lang.org/issues/13574. + # # @example # # # bad @@ -40,6 +44,18 @@ module Lint # # alias bar foo # + # # good + # alias foo foo + # def foo + # 1 + # end + # + # # good + # alias_method :foo, :foo + # def foo + # 1 + # end + # # @example AllCops:ActiveSupportExtensionsEnabled: false (default) # # # good @@ -113,11 +129,13 @@ def on_defs(node) # @!method method_alias?(node) def_node_matcher :method_alias?, <<~PATTERN - (alias (sym $_name) sym) + (alias (sym $_name) (sym $_original_name)) PATTERN def on_alias(node) - return unless (name = method_alias?(node)) + name, original_name = method_alias?(node) + return unless name && original_name + return if name == original_name return if node.ancestors.any?(&:if_type?) found_instance_method(node, name) @@ -125,7 +143,7 @@ def on_alias(node) # @!method alias_method?(node) def_node_matcher :alias_method?, <<~PATTERN - (send nil? :alias_method (sym $_name) _) + (send nil? :alias_method (sym $_name) (sym $_original_name)) PATTERN # @!method delegate_method?(node) @@ -140,7 +158,10 @@ def on_alias(node) def_node_matcher :sym_name, '(sym $_name)' def on_send(node) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity - if (name = alias_method?(node)) + name, original_name = alias_method?(node) + + if name && original_name + return if name == original_name return if node.ancestors.any?(&:if_type?) found_instance_method(node, name) diff --git a/lib/rubocop/cop/lint/literal_as_condition.rb b/lib/rubocop/cop/lint/literal_as_condition.rb index 9182e3fa8eb5..33fc8f9e56e7 100644 --- a/lib/rubocop/cop/lint/literal_as_condition.rb +++ b/lib/rubocop/cop/lint/literal_as_condition.rb @@ -123,7 +123,9 @@ def on_until_post(node) # rubocop:enable Metrics/AbcSize def on_case(case_node) - if case_node.condition + if (cond = case_node.condition) + return if !cond.falsey_literal? && !cond.truthy_literal? + check_case(case_node) else case_node.when_branches.each do |when_node| diff --git a/lib/rubocop/cop/lint/redundant_type_conversion.rb b/lib/rubocop/cop/lint/redundant_type_conversion.rb index 12d938e46b13..d8b54b2ee382 100644 --- a/lib/rubocop/cop/lint/redundant_type_conversion.rb +++ b/lib/rubocop/cop/lint/redundant_type_conversion.rb @@ -185,9 +185,9 @@ class RedundantTypeConversion < Base (hash (pair (sym :exception) false)) PATTERN - # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity def on_send(node) - return if hash_or_set_with_block?(node) + return if node.arguments.any? || hash_or_set_with_block?(node) receiver = find_receiver(node) return unless literal_receiver?(node, receiver) || @@ -198,10 +198,10 @@ def on_send(node) message = format(MSG, method: node.method_name) add_offense(node.loc.selector, message: message) do |corrector| - corrector.remove(node.loc.dot.join(node.loc.selector)) + corrector.remove(node.loc.dot.join(node.loc.end || node.loc.selector)) end end - # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity alias on_csend on_send private diff --git a/lib/rubocop/cop/naming/method_name.rb b/lib/rubocop/cop/naming/method_name.rb index b03b2230573e..17e5572b1fca 100644 --- a/lib/rubocop/cop/naming/method_name.rb +++ b/lib/rubocop/cop/naming/method_name.rb @@ -39,6 +39,20 @@ module Naming # # good # def foo_bar; end # + # # bad + # define_method :fooBar do + # end + # + # # good + # define_method :foo_bar do + # end + # + # # bad + # Struct.new(:fooBar) + # + # # good + # Struct.new(:foo_bar) + # # @example EnforcedStyle: camelCase # # bad # def foo_bar; end @@ -46,6 +60,20 @@ module Naming # # good # def fooBar; end # + # # bad + # define_method :foo_bar do + # end + # + # # good + # define_method :fooBar do + # end + # + # # bad + # Struct.new(:foo_bar) + # + # # good + # Struct.new(:fooBar) + # # @example ForbiddenIdentifiers: ['def', 'super'] # # bad # def def; end @@ -72,7 +100,46 @@ class MethodName < Base # @!method str_name(node) def_node_matcher :str_name, '(str $_name)' + # @!method new_struct?(node) + def_node_matcher :new_struct?, '(send (const {nil? cbase} :Struct) :new ...)' + def on_send(node) + if node.method?(:define_method) || node.method?(:define_singleton_method) + handle_define_method(node) + elsif new_struct?(node) + handle_new_struct(node) + else + handle_attr_accessor(node) + end + end + + def on_def(node) + return if node.operator_method? || matches_allowed_pattern?(node.method_name) + + if forbidden_name?(node.method_name.to_s) + register_forbidden_name(node) + else + check_name(node, node.method_name, node.loc.name) + end + end + alias on_defs on_def + + private + + def handle_define_method(node) + return unless node.first_argument&.type?(:str, :sym) + + handle_method_name(node, node.first_argument.value) + end + + def handle_new_struct(node) + arguments = node.first_argument&.str_type? ? node.arguments[1..] : node.arguments + arguments.select { |argument| argument.type?(:sym, :str) }.each do |name| + handle_method_name(name, name.value) + end + end + + def handle_attr_accessor(node) return unless (attrs = node.attribute_accessor?) attrs.last.each do |name_item| @@ -87,45 +154,53 @@ def on_send(node) end end - def on_def(node) - return if node.operator_method? || matches_allowed_pattern?(node.method_name) + def handle_method_name(node, name) + return if !name || matches_allowed_pattern?(name) - if forbidden_name?(node.method_name.to_s) + if forbidden_name?(name.to_s) register_forbidden_name(node) else - check_name(node, node.method_name, node.loc.name) + check_name(node, name, range_position(node)) end end - alias on_defs on_def - - private def forbidden_name?(name) forbidden_identifier?(name) || forbidden_pattern?(name) end + # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def register_forbidden_name(node) if node.any_def_type? name_node = node.loc.name method_name = node.method_name - else - attrs = node.attribute_accessor? + elsif node.literal? + name_node = node + method_name = node.value + elsif (attrs = node.attribute_accessor?) name_node = attrs.last.last method_name = attr_name(name_node) + else + name_node = node.first_argument + method_name = node.first_argument.value end message = format(MSG_FORBIDDEN, identifier: method_name) add_offense(name_node, message: message) end + # rubocop:enable Metrics/MethodLength, Metrics/AbcSize def attr_name(name_item) sym_name(name_item) || str_name(name_item) end def range_position(node) - selector_end_pos = node.loc.selector.end_pos + 1 - expr_end_pos = node.source_range.end_pos + if node.loc.respond_to?(:selector) + selector_end_pos = node.loc.selector.end_pos + 1 + expr_end_pos = node.source_range.end_pos - range_between(selector_end_pos, expr_end_pos) + range_between(selector_end_pos, expr_end_pos) + else + node.source_range + end end def message(style) diff --git a/lib/rubocop/cop/naming/predicate_method.rb b/lib/rubocop/cop/naming/predicate_method.rb index a055af57d2e8..ea682c544564 100644 --- a/lib/rubocop/cop/naming/predicate_method.rb +++ b/lib/rubocop/cop/naming/predicate_method.rb @@ -26,6 +26,11 @@ module Naming # guidelines. By default, `call` is allowed. The cop also has `AllowedPatterns` # configuration to allow method names by regular expression. # + # Although returning a call to another predicate method is treated as a boolean value, + # certain method names can be known to not return a boolean, despite ending in a `?` + # (for example, `Numeric#nonzero?` returns `self` or `nil`). These methods can be + # configured using `NonBooleanPredicates`. + # # The cop can furthermore be configured to allow all bang methods (method names # ending with `!`), with `AllowBangMethods: true` (default false). # @@ -164,7 +169,7 @@ def acceptable?(return_values) def unknown_method_call?(value) return false unless value.call_type? - !value.comparison_method? && !value.predicate_method? && !value.negation_method? + !method_returning_boolean?(value) end def return_values(node) @@ -190,7 +195,13 @@ def all_return_values_boolean?(return_values) def boolean_return?(value) return true if value.boolean_type? + + method_returning_boolean?(value) + end + + def method_returning_boolean?(value) return false unless value.call_type? + return false if wayward_predicate?(value.method_name) value.comparison_method? || value.predicate_method? || value.negation_method? end @@ -275,6 +286,17 @@ def allowed_bang_method?(node) def allow_bang_methods? cop_config.fetch('AllowBangMethods', false) end + + # If a method ending in `?` is known to not return a boolean value, + # (for example, `Numeric#nonzero?`) it should be treated as a non-boolean + # value, despite the method naming. + def wayward_predicate?(name) + wayward_predicates.include?(name.to_s) + end + + def wayward_predicates + Array(cop_config.fetch('WaywardPredicates', [])) + end end end end diff --git a/lib/rubocop/cop/security/eval.rb b/lib/rubocop/cop/security/eval.rb index ba0619cd4f2a..cc6b81fdea46 100644 --- a/lib/rubocop/cop/security/eval.rb +++ b/lib/rubocop/cop/security/eval.rb @@ -11,13 +11,14 @@ module Security # # eval(something) # binding.eval(something) + # Kernel.eval(something) class Eval < Base MSG = 'The use of `eval` is a serious security risk.' RESTRICT_ON_SEND = %i[eval].freeze # @!method eval?(node) def_node_matcher :eval?, <<~PATTERN - (send {nil? (send nil? :binding)} :eval $!str ...) + (send {nil? (send nil? :binding) (const {cbase nil?} :Kernel)} :eval $!str ...) PATTERN def on_send(node) diff --git a/lib/rubocop/cop/security/open.rb b/lib/rubocop/cop/security/open.rb index 625a0d5294ca..21382f401dd3 100644 --- a/lib/rubocop/cop/security/open.rb +++ b/lib/rubocop/cop/security/open.rb @@ -34,6 +34,7 @@ module Security # # good (literal strings) # open("foo.text") # URI.open("http://example.com") + # URI.parse(url).open class Open < Base MSG = 'The use of `%sopen` is a serious security risk.' RESTRICT_ON_SEND = %i[open].freeze diff --git a/lib/rubocop/cop/style/hash_conversion.rb b/lib/rubocop/cop/style/hash_conversion.rb index 666ef1b7710c..cf6afc3e0bc6 100644 --- a/lib/rubocop/cop/style/hash_conversion.rb +++ b/lib/rubocop/cop/style/hash_conversion.rb @@ -44,10 +44,10 @@ module Style class HashConversion < Base extend AutoCorrector - MSG_TO_H = 'Prefer ary.to_h to Hash[ary].' - MSG_LITERAL_MULTI_ARG = 'Prefer literal hash to Hash[arg1, arg2, ...].' - MSG_LITERAL_HASH_ARG = 'Prefer literal hash to Hash[key: value, ...].' - MSG_SPLAT = 'Prefer array_of_pairs.to_h to Hash[*array].' + MSG_TO_H = 'Prefer `ary.to_h` to `Hash[ary]`.' + MSG_LITERAL_MULTI_ARG = 'Prefer literal hash to `Hash[arg1, arg2, ...]`.' + MSG_LITERAL_HASH_ARG = 'Prefer literal hash to `Hash[key: value, ...]`.' + MSG_SPLAT = 'Prefer `array_of_pairs.to_h` to `Hash[*array]`.' RESTRICT_ON_SEND = %i[[]].freeze # @!method hash_from_array?(node) @@ -64,11 +64,11 @@ def on_send(node) # Hash[a1, a2, a3, a4] => {a1 => a2, a3 => a4} # ...but don't suggest correction if there is odd number of them (it is a bug) node.arguments.one? ? single_argument(node) : multi_argument(node) + ignore_node(node) end private - # rubocop:disable Metrics/MethodLength def single_argument(node) first_argument = node.first_argument if first_argument.hash_type? @@ -83,11 +83,8 @@ def single_argument(node) replacement = "(#{replacement})" if requires_parens?(first_argument) corrector.replace(node, "#{replacement}.to_h") end - - ignore_node(node) end end - # rubocop:enable Metrics/MethodLength def use_zip_method_without_argument?(first_argument) return false unless first_argument&.send_type? @@ -131,7 +128,9 @@ def multi_argument(node) corrector.replace(node, args_to_hash(node.arguments)) parent = node.parent - add_parentheses(parent, corrector) if parent&.send_type? && !parent.parenthesized? + if parent&.send_type? && !parent.method?(:to_h) && !parent.parenthesized? + add_parentheses(parent, corrector) + end end end end diff --git a/lib/rubocop/cop/style/it_block_parameter.rb b/lib/rubocop/cop/style/it_block_parameter.rb index 057005f8f579..7510344ed2a1 100644 --- a/lib/rubocop/cop/style/it_block_parameter.rb +++ b/lib/rubocop/cop/style/it_block_parameter.rb @@ -109,7 +109,7 @@ def on_itblock(node) private def find_block_variables(node, block_argument_name) - node.each_descendant(:lvar).select do |descendant| + node.body.each_descendant(:lvar).select do |descendant| descendant.source == block_argument_name end end diff --git a/lib/rubocop/cop/style/method_call_with_args_parentheses.rb b/lib/rubocop/cop/style/method_call_with_args_parentheses.rb index 2f291aca0002..8e6ff7b176f5 100644 --- a/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +++ b/lib/rubocop/cop/style/method_call_with_args_parentheses.rb @@ -132,6 +132,22 @@ module Style # bar :baz # end # + # @example AllowedMethods: ["puts", "print"] + # + # # good + # puts "Hello world" + # print "Hello world" + # # still enforces parentheses on other methods + # array.delete(e) + # + # @example AllowedPatterns: ["^assert"] + # + # # good + # assert_equal 'test', x + # assert_match(/foo/, bar) + # # still enforces parentheses on other methods + # array.delete(e) + # # @example AllowParenthesesInMultilineCall: false (default) # # # bad diff --git a/lib/rubocop/cop/style/redundant_fetch_block.rb b/lib/rubocop/cop/style/redundant_fetch_block.rb index e806b44f5898..c15b034eb489 100644 --- a/lib/rubocop/cop/style/redundant_fetch_block.rb +++ b/lib/rubocop/cop/style/redundant_fetch_block.rb @@ -49,7 +49,7 @@ class RedundantFetchBlock < Base (block $(call _ :fetch _) (args) - ${nil? #basic_literal? #const_type?}) + ${nil? basic_literal? const_type?}) PATTERN def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler @@ -71,14 +71,6 @@ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler private - def basic_literal?(node) - node&.basic_literal? - end - - def const_type?(node) - node&.const_type? - end - def should_not_check?(send, body) (body&.const_type? && !check_for_constant?) || (body&.str_type? && !check_for_string?) || diff --git a/lib/rubocop/cop/style/single_line_methods.rb b/lib/rubocop/cop/style/single_line_methods.rb index 74d01ff22594..9a6500cd5fd3 100644 --- a/lib/rubocop/cop/style/single_line_methods.rb +++ b/lib/rubocop/cop/style/single_line_methods.rb @@ -130,7 +130,10 @@ def method_body_source(method_body) end def require_parentheses?(method_body) - method_body.send_type? && !method_body.arguments.empty? && !method_body.comparison_method? + return false unless method_body.send_type? + return false if method_body.arithmetic_operation? + + !method_body.arguments.empty? && !method_body.comparison_method? end def disallow_endless_method_style? diff --git a/lib/rubocop/pending_cops_reporter.rb b/lib/rubocop/pending_cops_reporter.rb new file mode 100644 index 000000000000..82fdfe50ab35 --- /dev/null +++ b/lib/rubocop/pending_cops_reporter.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module RuboCop + # Reports information about pending cops that are not explicitly configured. + # + # This class is responsible for displaying warnings when new cops have been added to RuboCop + # but have not yet been enabled or disabled in the user's configuration. + # It provides a centralized way to determine whether such warnings should be shown, + # based on global flags or configuration settings. + class PendingCopsReporter + class << self + PENDING_BANNER = <<~BANNER + The following cops were added to RuboCop, but are not configured. Please set Enabled to either `true` or `false` in your `.rubocop.yml` file. + + Please also note that you can opt-in to new cops by default by adding this to your config: + AllCops: + NewCops: enable + BANNER + + attr_accessor :disable_pending_cops, :enable_pending_cops + + def warn_if_needed(config) + return if possible_new_cops?(config) + + pending_cops = pending_cops_only_qualified(config.pending_cops) + warn_on_pending_cops(pending_cops) unless pending_cops.empty? + end + + private + + def pending_cops_only_qualified(pending_cops) + pending_cops.select { |cop| Cop::Registry.qualified_cop?(cop.name) } + end + + def possible_new_cops?(config) + disable_pending_cops || enable_pending_cops || + config.disabled_new_cops? || config.enabled_new_cops? + end + + def warn_on_pending_cops(pending_cops) + warn Rainbow(PENDING_BANNER).yellow + + pending_cops.each { |cop| warn_pending_cop cop } + + warn Rainbow('For more information: https://docs.rubocop.org/rubocop/versioning.html').yellow + end + + def warn_pending_cop(cop) + version = cop.metadata['VersionAdded'] || 'N/A' + + warn Rainbow("#{cop.name}: # new in #{version}").yellow + warn Rainbow(' Enabled: true').yellow + end + end + end +end diff --git a/lib/rubocop/server/cache.rb b/lib/rubocop/server/cache.rb index 7b9586fa52ed..5748bca14d32 100644 --- a/lib/rubocop/server/cache.rb +++ b/lib/rubocop/server/cache.rb @@ -46,12 +46,14 @@ def project_dir_cache_key end # rubocop:disable Metrics/AbcSize - def restart_key + def restart_key(args_config_file_path: nil) lockfile_path = LOCKFILE_NAMES.map do |lockfile_name| Pathname(project_dir).join(lockfile_name) end.find(&:exist?) version_data = lockfile_path&.read || RuboCop::Version::STRING - config_data = Pathname(ConfigFinder.find_config_path(Dir.pwd)).read + config_data = Pathname( + args_config_file_path || ConfigFinder.find_config_path(Dir.pwd) + ).read yaml = load_erb_templated_yaml(config_data) inherit_from_data = inherit_from_data(yaml) diff --git a/lib/rubocop/server/client_command/base.rb b/lib/rubocop/server/client_command/base.rb index e8f768f40fa6..bd7c589295b6 100644 --- a/lib/rubocop/server/client_command/base.rb +++ b/lib/rubocop/server/client_command/base.rb @@ -38,6 +38,16 @@ def check_running_server warn 'RuboCop server is not running.' unless running end end + + class << self + def args_config_file_path + first_args_config_key_index = ARGV.index { |value| ['-c', '--config'].include?(value) } + + return if first_args_config_key_index.nil? + + ARGV[first_args_config_key_index + 1] + end + end end end end diff --git a/lib/rubocop/server/client_command/exec.rb b/lib/rubocop/server/client_command/exec.rb index 3a2c03d4012b..99b1b5f7b0a5 100644 --- a/lib/rubocop/server/client_command/exec.rb +++ b/lib/rubocop/server/client_command/exec.rb @@ -41,7 +41,8 @@ def ensure_server! end def incompatible_version? - Cache.version_path.read != Cache.restart_key + Cache.version_path.read != + Cache.restart_key(args_config_file_path: self.class.args_config_file_path) end def stderr diff --git a/lib/rubocop/server/client_command/start.rb b/lib/rubocop/server/client_command/start.rb index 2e9d02f66af5..218ca4982528 100644 --- a/lib/rubocop/server/client_command/start.rb +++ b/lib/rubocop/server/client_command/start.rb @@ -34,7 +34,7 @@ def run exit 0 end - Cache.write_version_file(Cache.restart_key) + write_version_file host = ENV.fetch('RUBOCOP_SERVER_HOST', '127.0.0.1') port = ENV.fetch('RUBOCOP_SERVER_PORT', 0) @@ -42,6 +42,16 @@ def run Server::Core.new.start(host, port, detach: @detach) end end + + private + + def write_version_file + Cache.write_version_file( + Cache.restart_key( + args_config_file_path: self.class.args_config_file_path + ) + ) + end end end end diff --git a/lib/rubocop/version.rb b/lib/rubocop/version.rb index e2a40b5b5c56..3662404a4fec 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.77.0' + STRING = '1.78.0' MSG = '%s (using %s, ' \ 'rubocop-ast %s, ' \ diff --git a/relnotes/v1.78.0.md b/relnotes/v1.78.0.md new file mode 100644 index 000000000000..5ee5ee6cea7b --- /dev/null +++ b/relnotes/v1.78.0.md @@ -0,0 +1,29 @@ +### New features + +* [#14331](https://github.com/rubocop/rubocop/pull/14331): Enhance `Naming/MethodName` cop to detect offenses within `define_method` calls. ([@viralpraxis][]) +* [#14325](https://github.com/rubocop/rubocop/pull/14325): Enhance `Naming/MethodName` cop to handle offenses within `Struct` members. ([@viralpraxis][]) +* [#14335](https://github.com/rubocop/rubocop/pull/14335): Enhance `Security/Eval` cop to detect `Kernel.eval` calls. ([@viralpraxis][]) + +### Bug fixes + +* [#14343](https://github.com/rubocop/rubocop/pull/14343): Fix autocorrect code for `Style/HashConversion` to avoid syntax error. ([@koic][]) +* [#14346](https://github.com/rubocop/rubocop/issues/14346): Avoid requiring parentheses for `Style/SingleLineMethods`. ([@koic][]) +* [#14339](https://github.com/rubocop/rubocop/pull/14339): Fix bug where specifying `--format` disables parallelization. ([@r7kamura][]) +* [#14300](https://github.com/rubocop/rubocop/pull/14300): Fix false positives for `Lint/DuplicateMethods` cop when self-alias trick is used. ([@viralpraxis][]) +* [#14329](https://github.com/rubocop/rubocop/issues/14329): Fix false positives for `Lint/LiteralAsCondition` when a literal is used inside `||` in `case` condition. ([@koic][]) +* [#14326](https://github.com/rubocop/rubocop/issues/14326): Fix additional autocorrection errors in `Style/HashConversion` for nested `Hash[]` calls. ([@dvandersluis][]) +* [#14031](https://github.com/rubocop/rubocop/issues/14031): Honor --config options on server mode. ([@steiley][]) +* [#14319](https://github.com/rubocop/rubocop/pull/14319): Fix the following incorrect autocorrect for `Lint/RedundantTypeConversion` when using parentheses with no arguments or any arguments. ([@koic][]) +* [#14336](https://github.com/rubocop/rubocop/issues/14336): Fix incorrect autocorrect for `Style/ItBlockParameter` when using a single numbered parameter after multiple numbered parameters in a method chain. ([@koic][]) +* [#11782](https://github.com/rubocop/rubocop/issues/11782): Move pending cops warning out of ConfigLoader. ([@nobuyo][]) + +### Changes + +* [#14318](https://github.com/rubocop/rubocop/issues/14318): Add `WaywardPredicates` config to `Naming/PredicateMethod` to handle methods that look like predicates but aren't. ([@dvandersluis][]) + +[@viralpraxis]: https://github.com/viralpraxis +[@koic]: https://github.com/koic +[@r7kamura]: https://github.com/r7kamura +[@dvandersluis]: https://github.com/dvandersluis +[@steiley]: https://github.com/steiley +[@nobuyo]: https://github.com/nobuyo diff --git a/spec/rubocop/cli/auto_gen_config_spec.rb b/spec/rubocop/cli/auto_gen_config_spec.rb index 59f866e63f44..b93d1e5e053e 100644 --- a/spec/rubocop/cli/auto_gen_config_spec.rb +++ b/spec/rubocop/cli/auto_gen_config_spec.rb @@ -1086,7 +1086,10 @@ def a; end actual = File.read('.rubocop_todo.yml').lines.grep_v(/^(#.*)?$/) expect(actual.join).to eq(expected) - expect(cli.run([])).to eq(0) + # NOTE: Reload CLI to ensure updated config with rubocop_todo is properly picked up. + # ConfigStore#for_pwd caches results; re-create CLI to avoid using stale config. + fresh_cli = RuboCop::CLI.new + expect(fresh_cli.run([])).to eq(0) end end @@ -1726,7 +1729,11 @@ def bar - '**/*.arb' - 'file.rb' YAML - expect(cli.run([])).to eq(0) + + # NOTE: Reload CLI to ensure updated config with rubocop_todo is properly picked up. + # ConfigStore#for_pwd caches results; re-create CLI to avoid using stale config. + fresh_cli = RuboCop::CLI.new + expect(fresh_cli.run([])).to eq(0) end end end diff --git a/spec/rubocop/config_loader_spec.rb b/spec/rubocop/config_loader_spec.rb index 00078c0deb17..b11548e4babf 100644 --- a/spec/rubocop/config_loader_spec.rb +++ b/spec/rubocop/config_loader_spec.rb @@ -2060,60 +2060,12 @@ def self.inject! end end - describe 'when pending cops exist', :isolated_environment do - subject(:from_file) { described_class.configuration_from_file('.rubocop.yml') } - - before do - create_empty_file('.rubocop.yml') - - # Setup similar to https://github.com/rubocop/rubocop-rspec/blob/master/lib/rubocop/rspec/inject.rb#L16 - # and https://github.com/runtastic/rt_rubocop_defaults/blob/master/lib/rt_rubocop_defaults/inject.rb#L21 - config = RuboCop::Config.new(parent_config) - described_class.instance_variable_set(:@default_configuration, config) - end - - context 'when NewCops is set in a required file' do - let(:parent_config) { { 'AllCops' => { 'NewCops' => 'enable' } } } - - it 'does not print a warning' do - expect(described_class).not_to receive(:warn_on_pending_cops) - from_file - end - end - - context 'when NewCops is not configured in a required file' do - let(:parent_config) { { 'AllCops' => { 'Exclude:' => ['coverage/**/*'] } } } - - context 'when `pending_cops_only_qualified` returns empty array' do - before do - allow(described_class).to receive(:pending_cops_only_qualified).and_return([]) - end - - it 'does not print a warning' do - expect(described_class).not_to receive(:warn_on_pending_cops) - from_file - end - end - - context 'when `pending_cops_only_qualified` returns not empty array' do - before do - allow(described_class).to receive(:pending_cops_only_qualified).and_return(['Foo/Bar']) - end - - it 'prints a warning' do - expect(described_class).to receive(:warn_on_pending_cops) - from_file - end - end - end - end - describe 'configuration for AssignmentInCondition' do describe 'AllowSafeAssignment' do it 'is enabled by default' do default_config = described_class.default_configuration symbol_name_config = default_config.for_cop('Lint/AssignmentInCondition') - expect(symbol_name_config['AllowSafeAssignment']).to be_truthy + expect(symbol_name_config['AllowSafeAssignment']).to be(true) end end end @@ -2129,14 +2081,14 @@ def self.inject! it 'requires the passed path' do config_path = described_class.configuration_file_for('.') described_class.configuration_from_file(config_path) - expect(defined?(MyClass)).to be_truthy + expect(defined?(MyClass)).to eq('constant') end it 'uses paths relative to the .rubocop.yml, not cwd' do config_path = described_class.configuration_file_for('.') Dir.chdir '..' do described_class.configuration_from_file(config_path) - expect(defined?(MyClass)).to be_truthy + expect(defined?(MyClass)).to eq('constant') end end end @@ -2154,7 +2106,7 @@ def self.inject! $LOAD_PATH.unshift(File.dirname(config_path)) Dir.chdir '..' do described_class.configuration_from_file(config_path) - expect(defined?(MyClass)).to be_truthy + expect(defined?(MyClass)).to eq('constant') end end end diff --git a/spec/rubocop/cop/annotation_comment_spec.rb b/spec/rubocop/cop/annotation_comment_spec.rb index 6f5533cd4c68..7c5758997bd1 100644 --- a/spec/rubocop/cop/annotation_comment_spec.rb +++ b/spec/rubocop/cop/annotation_comment_spec.rb @@ -36,19 +36,19 @@ context 'when annotated with a non keyword' do let(:text) { 'SOMETHING: note' } - it { is_expected.to be_falsey } + it { is_expected.to be_nil } end context 'when given as the first word of a sentence' do let(:text) { 'Todo in the future' } - it { is_expected.to be_falsey } + it { is_expected.to be(false) } end context 'when it includes a keyword' do let(:text) { 'TODO2' } - it { is_expected.to be_falsey } + it { is_expected.to be_nil } end end @@ -58,13 +58,13 @@ shared_examples 'correct' do |text| let(:text) { text } - it { is_expected.to be_truthy } + it { is_expected.to be(true) } end shared_examples 'incorrect' do |text| let(:text) { text } - it { is_expected.to be_falsey } + it { is_expected.to be(false) } end let(:colon) { true } diff --git a/spec/rubocop/cop/internal_affairs/example_description_spec.rb b/spec/rubocop/cop/internal_affairs/example_description_spec.rb index f482f92800d6..f7664bed17c7 100644 --- a/spec/rubocop/cop/internal_affairs/example_description_spec.rb +++ b/spec/rubocop/cop/internal_affairs/example_description_spec.rb @@ -137,6 +137,21 @@ RUBY end + it 'registers an offense when given an improper description for `allows`' do + expect_offense(<<~RUBY) + it 'allows the case' do + ^^^^^^^^^^^^^^^^^ Description does not match use of `expect_offense`. + expect_offense('code') + end + RUBY + + expect_correction(<<~RUBY) + it 'registers the case' do + expect_offense('code') + end + RUBY + end + it 'registers an offense when given an improper description for `register`' do expect_offense(<<~RUBY) it 'register the case' do diff --git a/spec/rubocop/cop/layout/multiline_assignment_layout_spec.rb b/spec/rubocop/cop/layout/multiline_assignment_layout_spec.rb index 5439f4f4150c..5b96f4640841 100644 --- a/spec/rubocop/cop/layout/multiline_assignment_layout_spec.rb +++ b/spec/rubocop/cop/layout/multiline_assignment_layout_spec.rb @@ -60,7 +60,7 @@ context 'configured supported types' do let(:supported_types) { %w[array] } - it 'allows supported types to be configured' do + it 'registers an offense when supported types are configured' do expect_offense(<<~RUBY) a, b = 4, ^^^^^^^^^ Right hand side of multi-line assignment is on the same line as the assignment operator `=`. @@ -182,7 +182,7 @@ context 'configured supported types' do let(:supported_types) { %w[array] } - it 'allows supported types to be configured' do + it 'registers an offense when supported types are configured' do expect_offense(<<~RUBY) a, b = ^^^^^^ Right hand side of multi-line assignment is not on the same line as the assignment operator `=`. diff --git a/spec/rubocop/cop/lint/duplicate_methods_spec.rb b/spec/rubocop/cop/lint/duplicate_methods_spec.rb index 8f52fb5aa604..75a0cd27657b 100644 --- a/spec/rubocop/cop/lint/duplicate_methods_spec.rb +++ b/spec/rubocop/cop/lint/duplicate_methods_spec.rb @@ -261,6 +261,17 @@ def some_method RUBY end + it "does not register an offense for duplicate self-alias in #{type}" do + expect_no_offenses(<<~RUBY, 'example.rb') + #{opening_line} + alias some_method some_method + def some_method + implement 1 + end + end + RUBY + end + it "doesn't register an offense for non-duplicate alias in #{type}" do expect_no_offenses(<<~RUBY) #{opening_line} @@ -284,6 +295,28 @@ def some_method RUBY end + it "does not register an offense for duplicate self-alias_method in #{type}" do + expect_no_offenses(<<~RUBY, 'example.rb') + #{opening_line} + alias_method :some_method, :some_method + def some_method + implement 1 + end + end + RUBY + end + + it "does not register an offense for duplicate self-alias_method with dynamic original name in #{type}" do + expect_no_offenses(<<~RUBY, 'example.rb') + #{opening_line} + alias_method :some_method, unknown() + def some_method + implement 1 + end + end + RUBY + end + it "accepts for non-duplicate alias_method in #{type}" do expect_no_offenses(<<~RUBY) #{opening_line} diff --git a/spec/rubocop/cop/lint/literal_as_condition_spec.rb b/spec/rubocop/cop/lint/literal_as_condition_spec.rb index 73fa4e15fa04..e9573c0af9af 100644 --- a/spec/rubocop/cop/lint/literal_as_condition_spec.rb +++ b/spec/rubocop/cop/lint/literal_as_condition_spec.rb @@ -323,7 +323,7 @@ RUBY end - it "accepts literal #{lit} in non-toplevel and/or" do + it "accepts literal #{lit} in non-toplevel and/or as an `if` condition" do expect_no_offenses(<<~RUBY) if (a || #{lit}).something top @@ -331,6 +331,15 @@ RUBY end + it "accepts literal #{lit} in non-toplevel and/or as a `case` condition" do + expect_no_offenses(<<~RUBY) + case a || #{lit} + when b + top + end + RUBY + end + it "registers an offense for `!#{lit}`" do expect_offense(<<~RUBY, lit: lit) !%{lit} @@ -636,6 +645,16 @@ foo RUBY end + + it "registers an offense for falsey literal #{lit} in `case`" do + expect_offense(<<~RUBY, lit: lit) + case %{lit} + ^{lit} Literal `#{lit}` appeared as a condition. + when x + top + end + RUBY + end end it 'registers an offense for `nil` literal in `until`' do diff --git a/spec/rubocop/cop/lint/redundant_type_conversion_spec.rb b/spec/rubocop/cop/lint/redundant_type_conversion_spec.rb index 8e3ad2a07a9a..cd5914cc7d7c 100644 --- a/spec/rubocop/cop/lint/redundant_type_conversion_spec.rb +++ b/spec/rubocop/cop/lint/redundant_type_conversion_spec.rb @@ -25,6 +25,24 @@ expect_correction("#{receiver}#{suffix}\n") end + + it "registers an offense and corrects on `#{receiver}.#{conversion}()`" do + expect_offense(<<~RUBY, receiver: receiver, conversion: conversion) + #{receiver}.#{conversion}() + _{receiver} ^{conversion} Redundant `#{conversion}` detected. + RUBY + + expect_correction("#{receiver}\n") + end + + it "registers an offense and corrects on `#{receiver}&.#{conversion}()`" do + expect_offense(<<~RUBY, receiver: receiver, conversion: conversion) + #{receiver}&.#{conversion}() + _{receiver} ^{conversion} Redundant `#{conversion}` detected. + RUBY + + expect_correction("#{receiver}\n") + end end shared_examples 'conversion' do |conversion| @@ -46,6 +64,16 @@ it_behaves_like 'accepted', "{}.#{conversion}" unless conversion == :to_h it_behaves_like 'accepted', "Set.new.#{conversion}" unless conversion == :to_set + it_behaves_like 'accepted', "'string'.#{conversion}(arg)" + it_behaves_like 'accepted', ":sym.#{conversion}(arg)" + it_behaves_like 'accepted', "1.#{conversion}(arg)" + it_behaves_like 'accepted', "1.0.#{conversion}(arg)" + it_behaves_like 'accepted', "1r.#{conversion}(arg)" + it_behaves_like 'accepted', "1i.#{conversion}(arg)" + it_behaves_like 'accepted', "[].#{conversion}(arg)" + it_behaves_like 'accepted', "{}.#{conversion}(arg)" + it_behaves_like 'accepted', "Set.new.#{conversion}(arg)" + it "does not register an offense when calling `#{conversion}` on an local variable named `#{conversion}`" do expect_no_offenses(<<~RUBY) #{conversion} = foo diff --git a/spec/rubocop/cop/naming/method_name_spec.rb b/spec/rubocop/cop/naming/method_name_spec.rb index 4fac551b4e79..222d0c630e77 100644 --- a/spec/rubocop/cop/naming/method_name_spec.rb +++ b/spec/rubocop/cop/naming/method_name_spec.rb @@ -131,6 +131,14 @@ def base.Start(aws_env, *args) end end + %w[Struct ::Struct].each do |class_name| + it "does not register an offense for member-less #{class_name}" do + expect_no_offenses(<<~RUBY) + #{class_name}.new() + RUBY + end + end + context 'when specifying `AllowedPatterns`' do let(:cop_config) do { @@ -258,6 +266,33 @@ def foo.%{identifier} expect_no_corrections end end + + context 'for define_method' do + it 'registers an offense when method with forbidden name is defined using `define_method`' do + expect_offense(<<~RUBY, identifier: identifier) + define_method :%{identifier} + ^^{identifier} `%{identifier}` is forbidden, use another method name instead. + RUBY + end + + it 'registers an offense when method with forbidden name is defined using `define_singleton_method`' do + expect_offense(<<~RUBY, identifier: identifier) + define_singleton_method :%{identifier} + ^^{identifier} `%{identifier}` is forbidden, use another method name instead. + RUBY + end + end + + context 'for `Struct` members' do + it 'registers an offense when member with forbidden name is defined' do + expect_offense(<<~RUBY, identifier: identifier) + Struct.new(:%{identifier}) + ^^{identifier} `%{identifier}` is forbidden, use another method name instead. + RUBY + + expect_no_corrections + end + end end end @@ -303,6 +338,67 @@ def %{identifier}; true; end expect_no_corrections end end + + context 'for define_method' do + it 'registers an offense when method with forbidden name is defined using `define_method`' do + expect_offense(<<~RUBY, identifier: identifier) + define_method :%{identifier} + ^^{identifier} `%{identifier}` is forbidden, use another method name instead. + RUBY + end + + it 'registers an offense when method with forbidden name is defined using `define_singleton_method`' do + expect_offense(<<~RUBY, identifier: identifier) + define_singleton_method :%{identifier} + ^^{identifier} `%{identifier}` is forbidden, use another method name instead. + RUBY + end + end + + context 'for `Struct` members' do + it 'registers an offense when member with forbidden name is defined' do + expect_offense(<<~RUBY, identifier: identifier) + Struct.new(:%{identifier}) + ^^{identifier} `%{identifier}` is forbidden, use another method name instead. + RUBY + + expect_no_corrections + end + end + end + end + + shared_examples 'define_method method call' do |enforced_style, identifier| + %i[define_method define_singleton_method].each do |name| + it 'registers an offense when method name is passed as a symbol' do + expect_offense(<<~RUBY, name: name, enforced_style: enforced_style, identifier: identifier) + %{name} :%{identifier} do + _{name} ^^{identifier} Use %{enforced_style} for method names. + end + RUBY + end + + it 'registers an offense when method name is passed as a string' do + expect_offense(<<~RUBY, name: name, enforced_style: enforced_style, identifier: identifier) + %{name} '%{identifier}' do + _{name} ^^{identifier}^ Use %{enforced_style} for method names. + end + RUBY + end + + it 'does not register an offense when `define_method` is called without any arguments`' do + expect_no_offenses(<<~RUBY) + #{name} do + end + RUBY + end + + it 'does not register an offense when `define_method` is called with a variable`' do + expect_no_offenses(<<~RUBY) + #{name} foo do + end + RUBY + end end end @@ -375,11 +471,28 @@ def self.fooBar RUBY end + it 'registers an offense for `Struct` camelCase member' do + expect_offense(<<~RUBY) + Struct.new("camelCase", :snake_case, var, *args, :camelCase, :snake_case_2, "camelCase2") + ^^^^^^^^^^ Use snake_case for method names. + ^^^^^^^^^^^^ Use snake_case for method names. + RUBY + end + + it 'registers an offense for `::Struct` camelCase member' do + expect_offense(<<~RUBY) + ::Struct.new("camelCase", :snake_case, var, *args, :camelCase, :snake_case_2, "camelCase2") + ^^^^^^^^^^ Use snake_case for method names. + ^^^^^^^^^^^^ Use snake_case for method names. + RUBY + end + include_examples 'never accepted', 'snake_case' include_examples 'always accepted', 'snake_case' include_examples 'multiple attr methods', 'snake_case' include_examples 'forbidden identifiers', 'super' include_examples 'forbidden patterns', '_v1\z', 'api_v1' + include_examples 'define_method method call', 'snake_case', 'fooBar' end context 'when configured for camelCase' do @@ -453,11 +566,28 @@ def self.foo_bar RUBY end + it 'registers an offense for `Struct` camelCase member' do + expect_offense(<<~RUBY) + Struct.new("foo_bar", var, *args, :snake_case, :camelCase, :snake_case_2, "camelCase2") + ^^^^^^^^^^^ Use camelCase for method names. + ^^^^^^^^^^^^^ Use camelCase for method names. + RUBY + end + + it 'registers an offense for `::Struct` camelCase member' do + expect_offense(<<~RUBY) + ::Struct.new("foo_bar", var, *args, :snake_case, :camelCase, :snake_case_2, "camelCase2") + ^^^^^^^^^^^ Use camelCase for method names. + ^^^^^^^^^^^^^ Use camelCase for method names. + RUBY + end + include_examples 'always accepted', 'camelCase' include_examples 'never accepted', 'camelCase' include_examples 'multiple attr methods', 'camelCase' include_examples 'forbidden identifiers', 'super' include_examples 'forbidden patterns', '_gen\d+\z', 'user_gen1' + include_examples 'define_method method call', 'camelCase', 'foo_bar' end it 'accepts for non-ascii characters' do diff --git a/spec/rubocop/cop/naming/predicate_method_spec.rb b/spec/rubocop/cop/naming/predicate_method_spec.rb index 4501fa748f15..4223a9822049 100644 --- a/spec/rubocop/cop/naming/predicate_method_spec.rb +++ b/spec/rubocop/cop/naming/predicate_method_spec.rb @@ -4,12 +4,14 @@ let(:allowed_methods) { [] } let(:allowed_patterns) { [] } let(:allow_bang_methods) { false } + let(:wayward_predicates) { [] } let(:cop_config) do { 'Mode' => mode, 'AllowedMethods' => allowed_methods, 'AllowedPatterns' => allowed_patterns, - 'AllowBangMethods' => allow_bang_methods + 'AllowBangMethods' => allow_bang_methods, + 'WaywardPredicates' => wayward_predicates } end @@ -584,6 +586,12 @@ def save! RUBY end end + + context 'with WaywardPredicates' do + let(:wayward_predicates) { %w[nonzero?] } + + it_behaves_like 'acceptable', 'nonzero?' + end end context 'with Mode: conservative' do diff --git a/spec/rubocop/cop/offense_spec.rb b/spec/rubocop/cop/offense_spec.rb index 4275223fedbe..82ec5cc8e70d 100644 --- a/spec/rubocop/cop/offense_spec.rb +++ b/spec/rubocop/cop/offense_spec.rb @@ -35,7 +35,7 @@ o1 = described_class.new(:convention, location, 'message', 'CopName') o2 = described_class.new(:convention, location, 'message', 'CopName') - expect(o1 == o2).to be_truthy + expect(o1 == o2).to be(true) end it 'is frozen' do diff --git a/spec/rubocop/cop/security/eval_spec.rb b/spec/rubocop/cop/security/eval_spec.rb index f89a227d607f..f89af3554d3c 100644 --- a/spec/rubocop/cop/security/eval_spec.rb +++ b/spec/rubocop/cop/security/eval_spec.rb @@ -22,6 +22,20 @@ RUBY end + it 'registers an offense for `Kernel.eval`' do + expect_offense(<<~RUBY) + Kernel.eval something + ^^^^ The use of `eval` is a serious security risk. + RUBY + end + + it 'registers an offense for `::Kernel.eval`' do + expect_offense(<<~RUBY) + ::Kernel.eval something + ^^^^ The use of `eval` is a serious security risk. + RUBY + end + it 'registers an offense for eval with string that has an interpolation' do expect_offense(<<~'RUBY') eval "something#{foo}" diff --git a/spec/rubocop/cop/style/hash_conversion_spec.rb b/spec/rubocop/cop/style/hash_conversion_spec.rb index 2bbd9311f66d..36730290e1a5 100644 --- a/spec/rubocop/cop/style/hash_conversion_spec.rb +++ b/spec/rubocop/cop/style/hash_conversion_spec.rb @@ -4,7 +4,7 @@ it 'reports an offense for single-argument Hash[]' do expect_offense(<<~RUBY) Hash[ary] - ^^^^^^^^^ Prefer ary.to_h to Hash[ary]. + ^^^^^^^^^ Prefer `ary.to_h` to `Hash[ary]`. RUBY expect_correction(<<~RUBY) @@ -15,7 +15,7 @@ it 'reports different offense for multi-argument Hash[]' do expect_offense(<<~RUBY) Hash[a, b, c, d] - ^^^^^^^^^^^^^^^^ Prefer literal hash to Hash[arg1, arg2, ...]. + ^^^^^^^^^^^^^^^^ Prefer literal hash to `Hash[arg1, arg2, ...]`. RUBY expect_correction(<<~RUBY) @@ -26,7 +26,7 @@ it 'reports different offense for hash argument Hash[]' do expect_offense(<<~RUBY) Hash[a: b, c: d] - ^^^^^^^^^^^^^^^^ Prefer literal hash to Hash[key: value, ...]. + ^^^^^^^^^^^^^^^^ Prefer literal hash to `Hash[key: value, ...]`. RUBY expect_correction(<<~RUBY) @@ -37,7 +37,7 @@ it 'reports different offense for hash argument Hash[] as a method argument with parentheses' do expect_offense(<<~RUBY) do_something(Hash[a: b, c: d], 42) - ^^^^^^^^^^^^^^^^ Prefer literal hash to Hash[key: value, ...]. + ^^^^^^^^^^^^^^^^ Prefer literal hash to `Hash[key: value, ...]`. RUBY expect_correction(<<~RUBY) @@ -48,7 +48,7 @@ it 'reports different offense for hash argument Hash[] as a method argument without parentheses' do expect_offense(<<~RUBY) do_something Hash[a: b, c: d], 42 - ^^^^^^^^^^^^^^^^ Prefer literal hash to Hash[key: value, ...]. + ^^^^^^^^^^^^^^^^ Prefer literal hash to `Hash[key: value, ...]`. RUBY expect_correction(<<~RUBY) @@ -59,7 +59,7 @@ it 'reports different offense for empty Hash[]' do expect_offense(<<~RUBY) Hash[] - ^^^^^^ Prefer literal hash to Hash[arg1, arg2, ...]. + ^^^^^^ Prefer literal hash to `Hash[arg1, arg2, ...]`. RUBY expect_correction(<<~RUBY) @@ -70,7 +70,7 @@ it 'registers and corrects an offense when using multi-argument `Hash[]` as a method argument' do expect_offense(<<~RUBY) do_something Hash[a, b, c, d], arg - ^^^^^^^^^^^^^^^^ Prefer literal hash to Hash[arg1, arg2, ...]. + ^^^^^^^^^^^^^^^^ Prefer literal hash to `Hash[arg1, arg2, ...]`. RUBY expect_correction(<<~RUBY) @@ -81,7 +81,7 @@ it 'does not try to correct multi-argument Hash with odd number of arguments' do expect_offense(<<~RUBY) Hash[a, b, c] - ^^^^^^^^^^^^^ Prefer literal hash to Hash[arg1, arg2, ...]. + ^^^^^^^^^^^^^ Prefer literal hash to `Hash[arg1, arg2, ...]`. RUBY expect_no_corrections @@ -90,7 +90,7 @@ it 'wraps complex statements in parens if needed' do expect_offense(<<~RUBY) Hash[a.foo :bar] - ^^^^^^^^^^^^^^^^ Prefer ary.to_h to Hash[ary]. + ^^^^^^^^^^^^^^^^ Prefer `ary.to_h` to `Hash[ary]`. RUBY expect_correction(<<~RUBY) @@ -101,7 +101,7 @@ it 'registers and corrects an offense when using argumentless `zip` without parentheses in `Hash[]`' do expect_offense(<<~RUBY) Hash[array.zip] - ^^^^^^^^^^^^^^^ Prefer ary.to_h to Hash[ary]. + ^^^^^^^^^^^^^^^ Prefer `ary.to_h` to `Hash[ary]`. RUBY expect_correction(<<~RUBY) @@ -112,7 +112,7 @@ it 'registers and corrects an offense when using argumentless `zip` with parentheses in `Hash[]`' do expect_offense(<<~RUBY) Hash[array.zip()] - ^^^^^^^^^^^^^^^^^ Prefer ary.to_h to Hash[ary]. + ^^^^^^^^^^^^^^^^^ Prefer `ary.to_h` to `Hash[ary]`. RUBY expect_correction(<<~RUBY) @@ -123,7 +123,7 @@ it 'reports different offense for Hash[a || b]' do expect_offense(<<~RUBY) Hash[a || b] - ^^^^^^^^^^^^ Prefer ary.to_h to Hash[ary]. + ^^^^^^^^^^^^ Prefer `ary.to_h` to `Hash[ary]`. RUBY expect_correction(<<~RUBY) @@ -134,7 +134,7 @@ it 'reports different offense for Hash[(a || b)]' do expect_offense(<<~RUBY) Hash[(a || b)] - ^^^^^^^^^^^^^^ Prefer ary.to_h to Hash[ary]. + ^^^^^^^^^^^^^^ Prefer `ary.to_h` to `Hash[ary]`. RUBY expect_correction(<<~RUBY) @@ -145,7 +145,7 @@ it 'reports different offense for Hash[a && b]' do expect_offense(<<~RUBY) Hash[a && b] - ^^^^^^^^^^^^ Prefer ary.to_h to Hash[ary]. + ^^^^^^^^^^^^ Prefer `ary.to_h` to `Hash[ary]`. RUBY expect_correction(<<~RUBY) @@ -156,7 +156,7 @@ it 'reports different offense for Hash[(a && b)]' do expect_offense(<<~RUBY) Hash[(a && b)] - ^^^^^^^^^^^^^^ Prefer ary.to_h to Hash[ary]. + ^^^^^^^^^^^^^^ Prefer `ary.to_h` to `Hash[ary]`. RUBY expect_correction(<<~RUBY) @@ -167,7 +167,7 @@ it 'registers and corrects an offense when using `zip` with argument in `Hash[]`' do expect_offense(<<~RUBY) Hash[array.zip([1, 2, 3])] - ^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer ary.to_h to Hash[ary]. + ^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `ary.to_h` to `Hash[ary]`. RUBY expect_correction(<<~RUBY) @@ -178,7 +178,7 @@ it 'reports an offense when using nested `Hash[]` without arguments' do expect_offense(<<~RUBY) Hash[Hash[]] - ^^^^^^^^^^^^ Prefer ary.to_h to Hash[ary]. + ^^^^^^^^^^^^ Prefer `ary.to_h` to `Hash[ary]`. RUBY expect_correction(<<~RUBY) @@ -189,7 +189,7 @@ it 'reports an offense when using nested `Hash[]` with arguments' do expect_offense(<<~RUBY) Hash[Hash[k, v]] - ^^^^^^^^^^^^^^^^ Prefer ary.to_h to Hash[ary]. + ^^^^^^^^^^^^^^^^ Prefer `ary.to_h` to `Hash[ary]`. RUBY expect_correction(<<~RUBY) @@ -197,6 +197,28 @@ RUBY end + it 'registers an offense and corrects nested `Hash[]` calls with multiple arguments' do + expect_offense(<<~RUBY) + Hash[1, Hash[k, v]] + ^^^^^^^^^^^^^^^^^^^ Prefer literal hash to `Hash[arg1, arg2, ...]`. + RUBY + + expect_correction(<<~RUBY) + {1 => Hash[k, v]} + RUBY + end + + it 'reports an offense for `Hash[].to_h`' do + expect_offense(<<~RUBY) + Hash[].to_h + ^^^^^^ Prefer literal hash to `Hash[arg1, arg2, ...]`. + RUBY + + expect_correction(<<~RUBY) + {}.to_h + RUBY + end + context 'AllowSplatArgument: true' do let(:cop_config) { { 'AllowSplatArgument' => true } } @@ -213,7 +235,7 @@ it 'reports uncorrectable offense for unpacked array' do expect_offense(<<~RUBY) Hash[*ary] - ^^^^^^^^^^ Prefer array_of_pairs.to_h to Hash[*array]. + ^^^^^^^^^^ Prefer `array_of_pairs.to_h` to `Hash[*array]`. RUBY expect_no_corrections diff --git a/spec/rubocop/cop/style/it_block_parameter_spec.rb b/spec/rubocop/cop/style/it_block_parameter_spec.rb index 2cb6ff34c6ac..4f63ac814e1d 100644 --- a/spec/rubocop/cop/style/it_block_parameter_spec.rb +++ b/spec/rubocop/cop/style/it_block_parameter_spec.rb @@ -102,6 +102,17 @@ RUBY end + it 'registers an offense when using a single numbered parameter after multiple numbered parameters in a method chain' do + expect_offense(<<~RUBY) + foo { bar(_1, _2) }.baz { qux(_1) } + ^^ Use `it` block parameter. + RUBY + + expect_correction(<<~RUBY) + foo { bar(_1, _2) }.baz { qux(it) } + RUBY + end + it 'does not register an offense when using `it` block parameters' do expect_no_offenses(<<~RUBY) block { do_something(it) } diff --git a/spec/rubocop/cop/style/single_line_methods_spec.rb b/spec/rubocop/cop/style/single_line_methods_spec.rb index 630384d166b8..2195bb8a6b90 100644 --- a/spec/rubocop/cop/style/single_line_methods_spec.rb +++ b/spec/rubocop/cop/style/single_line_methods_spec.rb @@ -232,6 +232,14 @@ def #{op}(other) = self #{op} other end end + RuboCop::AST::MethodDispatchNode.const_get(:ARITHMETIC_OPERATORS).each do |op| + it "corrects to an endless class method definition when using #{op}" do + expect_correction(<<~RUBY.strip, source: "def foo() bar #{op} baz end") + def foo() = bar #{op} baz + RUBY + end + end + it 'does not to an endless class method definition when using `return`' do expect_correction(<<~RUBY.strip, source: 'def foo(argument) return bar(argument); end') def foo(argument)#{trailing_whitespace} diff --git a/spec/rubocop/cop/team_spec.rb b/spec/rubocop/cop/team_spec.rb index b8f166297b48..19cc204e5375 100644 --- a/spec/rubocop/cop/team_spec.rb +++ b/spec/rubocop/cop/team_spec.rb @@ -59,13 +59,13 @@ def a context 'when the option argument of .mobilize is omitted' do subject { described_class.mobilize(cop_classes, config).autocorrect? } - it { is_expected.to be_falsey } + it { is_expected.to be_nil } end context 'when { autocorrect: true } is passed to .mobilize' do let(:options) { { autocorrect: true } } - it { is_expected.to be_truthy } + it { is_expected.to be(true) } end end @@ -75,13 +75,13 @@ def a context 'when the option argument of .mobilize is omitted' do subject { described_class.mobilize(cop_classes, config).debug? } - it { is_expected.to be_falsey } + it { is_expected.to be_nil } end context 'when { debug: true } is passed to .mobilize' do let(:options) { { debug: true } } - it { is_expected.to be_truthy } + it { is_expected.to be(true) } end end diff --git a/spec/rubocop/cop/variable_force/variable_spec.rb b/spec/rubocop/cop/variable_force/variable_spec.rb index dbac892ea790..1664f298833b 100644 --- a/spec/rubocop/cop/variable_force/variable_spec.rb +++ b/spec/rubocop/cop/variable_force/variable_spec.rb @@ -25,12 +25,12 @@ let(:variable) { described_class.new(name, declaration_node, scope) } context 'when the variable is not assigned' do - it { is_expected.to be_falsey } + it { is_expected.to be(false) } context 'and the variable is referenced' do before { variable.reference!(s(:lvar, name)) } - it { is_expected.to be_truthy } + it { is_expected.to be(true) } end end @@ -38,13 +38,13 @@ before { variable.assign(s(:lvasgn, name)) } context 'and the variable is not yet referenced' do - it { is_expected.to be_falsey } + it { is_expected.to be(false) } end context 'and the variable is referenced' do before { variable.reference!(s(:lvar, name)) } - it { is_expected.to be_truthy } + it { is_expected.to be(true) } end end end diff --git a/spec/rubocop/formatter/json_formatter_spec.rb b/spec/rubocop/formatter/json_formatter_spec.rb index bfb7c519f6f3..66e29534261e 100644 --- a/spec/rubocop/formatter/json_formatter_spec.rb +++ b/spec/rubocop/formatter/json_formatter_spec.rb @@ -121,11 +121,11 @@ end it 'sets Offense#correctable? value for :correctable key' do - expect(hash[:correctable]).to be_truthy + expect(hash[:correctable]).to be(true) end it 'sets Offense#corrected? value for :corrected key' do - expect(hash[:corrected]).to be_truthy + expect(hash[:corrected]).to be(true) end it 'sets value of #hash_for_location for :location key' do diff --git a/spec/rubocop/pending_cops_reporter_spec.rb b/spec/rubocop/pending_cops_reporter_spec.rb new file mode 100644 index 000000000000..9b4c24a701dd --- /dev/null +++ b/spec/rubocop/pending_cops_reporter_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::PendingCopsReporter do + include FileHelper + + describe 'when pending cops exist', :isolated_environment do + subject(:report_pending_cops) { described_class.warn_if_needed(config) } + + let(:config) { RuboCop::Config.new(parent_config) } + + before do + create_empty_file('.rubocop.yml') + + # Setup similar to https://github.com/rubocop/rubocop-rspec/blob/master/lib/rubocop/rspec/inject.rb#L16 + # and https://github.com/runtastic/rt_rubocop_defaults/blob/master/lib/rt_rubocop_defaults/inject.rb#L21 + loader = RuboCop::ConfigLoader.configuration_from_file('.rubocop.yml') + loader.instance_variable_set(:@default_configuration, config) + end + + context 'when NewCops is set in a required file' do + let(:parent_config) { { 'AllCops' => { 'NewCops' => 'enable' } } } + + it 'does not print a warning' do + expect(described_class).not_to receive(:warn_on_pending_cops) + report_pending_cops + end + end + + context 'when NewCops is not configured in a required file' do + let(:parent_config) { { 'AllCops' => { 'Exclude:' => ['coverage/**/*'] } } } + + context 'when `pending_cops_only_qualified` returns empty array' do + before do + allow(described_class).to receive(:pending_cops_only_qualified).and_return([]) + end + + it 'does not print a warning' do + expect(described_class).not_to receive(:warn_on_pending_cops) + report_pending_cops + end + end + + context 'when `pending_cops_only_qualified` returns not empty array' do + before do + allow(described_class).to receive(:pending_cops_only_qualified).and_return(['Foo/Bar']) + end + + it 'prints a warning' do + expect(described_class).to receive(:warn_on_pending_cops) + report_pending_cops + end + end + end + end +end diff --git a/spec/rubocop/server/cache_spec.rb b/spec/rubocop/server/cache_spec.rb index f83824bf4e54..5457d252ce98 100644 --- a/spec/rubocop/server/cache_spec.rb +++ b/spec/rubocop/server/cache_spec.rb @@ -23,7 +23,7 @@ it 'is the specified path' do if RuboCop::Platform.windows? - expect(cache_class.cache_path).to eq('C:/tmp/rubocop_cache/server') + expect(cache_class.cache_path).to eq('D:/tmp/rubocop_cache/server') else expect(cache_class.cache_path).to eq('/tmp/rubocop_cache/server') end @@ -62,7 +62,7 @@ expect(described_class).to receive(:require).with('yaml') if RuboCop::Platform.windows? - expect(cache_class.cache_path).to eq(cache_path.prepend('C:')) + expect(cache_class.cache_path).to eq(cache_path.prepend('D:')) else expect(cache_class.cache_path).to eq(cache_path) end @@ -90,7 +90,7 @@ YAML if RuboCop::Platform.windows? - expect(cache_class.cache_path).to eq(cache_path.prepend('C:')) + expect(cache_class.cache_path).to eq(cache_path.prepend('D:')) else expect(cache_class.cache_path).to eq(cache_path) end @@ -109,7 +109,7 @@ YAML if RuboCop::Platform.windows? - expect(cache_class.cache_path).to eq(File.join('C:/tmp', 'rubocop_cache', 'server')) + expect(cache_class.cache_path).to eq(File.join('D:/tmp', 'rubocop_cache', 'server')) else expect(cache_class.cache_path).to eq(File.join('/tmp', 'rubocop_cache', 'server')) end @@ -136,7 +136,7 @@ it 'contains the root from `RUBOCOP_CACHE_ROOT`' do if RuboCop::Platform.windows? - expect(cache_class.cache_path).to eq(cache_path.prepend('C:')) + expect(cache_class.cache_path).to eq(cache_path.prepend('D:')) else expect(cache_class.cache_path).to eq(cache_path) end @@ -159,7 +159,7 @@ it 'contains the root from `RUBOCOP_CACHE_ROOT`' do if RuboCop::Platform.windows? - expect(cache_class.cache_path).to eq(cache_path.prepend('C:')) + expect(cache_class.cache_path).to eq(cache_path.prepend('D:')) else expect(cache_class.cache_path).to eq(cache_path) end @@ -173,7 +173,7 @@ it 'contains the root from cache root path' do if RuboCop::Platform.windows? - expect(cache_class.cache_path).to eq(File.join('C:/tmp', 'rubocop_cache', 'server')) + expect(cache_class.cache_path).to eq(File.join('D:/tmp', 'rubocop_cache', 'server')) else expect(cache_class.cache_path).to eq(File.join('/tmp', 'rubocop_cache', 'server')) end @@ -201,7 +201,7 @@ it 'contains the root from `XDG_CACHE_HOME`' do if RuboCop::Platform.windows? - expect(cache_class.cache_path).to eq(cache_path.prepend('C:')) + expect(cache_class.cache_path).to eq(cache_path.prepend('D:')) else expect(cache_class.cache_path).to eq(cache_path) end @@ -215,7 +215,7 @@ it 'contains the root from cache root path' do if RuboCop::Platform.windows? - expect(cache_class.cache_path).to eq(File.join('C:/tmp', 'rubocop_cache', 'server')) + expect(cache_class.cache_path).to eq(File.join('D:/tmp', 'rubocop_cache', 'server')) else expect(cache_class.cache_path).to eq(File.join('/tmp', 'rubocop_cache', 'server')) end @@ -275,8 +275,11 @@ unless RuboCop::Platform.windows? describe '.restart_key', :isolated_environment do - subject(:restart_key) { described_class.restart_key } + subject(:restart_key) do + described_class.restart_key(args_config_file_path: args_config_file_path) + end + let(:args_config_file_path) { nil } let(:hexdigest) do Digest::SHA1.hexdigest(contents) end @@ -398,6 +401,22 @@ end end end + + context 'when args_config_file_path is specified' do + let(:args_config_file_path) { '.rubocop_todo.yml' } + let(:contents) do + RuboCop::Version::STRING + File.read('.rubocop_todo.yml') + end + + before do + create_file('.rubocop_todo.yml', <<~YAML) + Metrics/ClassLength: + Max: 192 + YAML + end + + it { expect(restart_key).to eq(hexdigest) } + end end describe '.pid_running?', :isolated_environment do diff --git a/spec/rubocop/server/rubocop_server_spec.rb b/spec/rubocop/server/rubocop_server_spec.rb index 8d15e0d1606c..9a5c8dc88f45 100644 --- a/spec/rubocop/server/rubocop_server_spec.rb +++ b/spec/rubocop/server/rubocop_server_spec.rb @@ -45,6 +45,42 @@ end end + context 'when using --config option after update specified config file' do + it 'displays a restart information message' do + create_file('.rubocop_todo.yml', <<~RUBY) + AllCops: + NewCops: enable + SuggestExtensions: false + Layout/LineLength: + Max: 100 + RUBY + + create_file('example.rb', <<~RUBY) + # frozen_string_literal: true + + x = 0 + puts x + RUBY + + options = '--server --only Style/StringLiterals --config .rubocop_todo.yml example.rb' + `ruby -I . \"#{rubocop}\" #{options}` + + # Update .rubocop_todo.yml + create_file('.rubocop_todo.yml', <<~RUBY) + AllCops: + NewCops: enable + SuggestExtensions: false + Layout/LineLength: + Max: 101 + RUBY + + _stdout, stderr, _status = Open3.capture3("ruby -I . \"#{rubocop}\" #{options}") + expect(stderr).to start_with( + 'RuboCop version incompatibility found, RuboCop server restarting...' + ) + end + end + context 'when using `--server` with `--stderr`' do it 'sends corrected source to stdout and rest to stderr' do create_file('example.rb', <<~RUBY) diff --git a/spec/support/suppress_pending_warning.rb b/spec/support/suppress_pending_warning.rb index 13eb73df7c81..3c464aeaede8 100644 --- a/spec/support/suppress_pending_warning.rb +++ b/spec/support/suppress_pending_warning.rb @@ -2,11 +2,11 @@ # # This is a file that supports testing. An open class has been applied to -# `RuboCop::ConfigLoader.warn_on_pending_cops` to suppress pending cop +# `RuboCop::PendingCopsReporter.warn_on_pending_cops` to suppress pending cop # warnings during testing. # module RuboCop - class ConfigLoader + class PendingCopsReporter class << self remove_method :warn_on_pending_cops def warn_on_pending_cops(config)