diff --git a/CHANGELOG.md b/CHANGELOG.md index 06210e20d06..16f9c852a7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## master (unreleased) +### New features + +* [#8474](https://github.com/rubocop-hq/rubocop/pull/8474): Add new `Lint/DuplicateRequire` cop. ([@fatkodima][]) + ### Changes * [#8362](https://github.com/rubocop-hq/rubocop/issues/8362): Add numbers of correctable offenses to summary. ([@nguyenquangminh0711][]) diff --git a/config/default.yml b/config/default.yml index 18f78270b33..8cae2bd6398 100644 --- a/config/default.yml +++ b/config/default.yml @@ -1427,6 +1427,11 @@ Lint/DuplicateMethods: Enabled: true VersionAdded: '0.29' +Lint/DuplicateRequire: + Description: 'Check for duplicate `require`s and `require_relative`s.' + Enabled: pending + VersionAdded: '0.90' + Lint/DuplicateRescueException: Description: 'Checks that there are no repeated exceptions used in `rescue` expressions.' Enabled: pending diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index f900a87f38b..4ab9e285baa 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -197,6 +197,7 @@ In the following section you find all available cops: * xref:cops_lint.adoc#lintduplicateelsifcondition[Lint/DuplicateElsifCondition] * xref:cops_lint.adoc#lintduplicatehashkey[Lint/DuplicateHashKey] * xref:cops_lint.adoc#lintduplicatemethods[Lint/DuplicateMethods] +* xref:cops_lint.adoc#lintduplicaterequire[Lint/DuplicateRequire] * xref:cops_lint.adoc#lintduplicaterescueexception[Lint/DuplicateRescueException] * xref:cops_lint.adoc#linteachwithobjectargument[Lint/EachWithObjectArgument] * xref:cops_lint.adoc#lintelselayout[Lint/ElseLayout] diff --git a/docs/modules/ROOT/pages/cops_lint.adoc b/docs/modules/ROOT/pages/cops_lint.adoc index 07f0c9f420f..c267a2f0f88 100644 --- a/docs/modules/ROOT/pages/cops_lint.adoc +++ b/docs/modules/ROOT/pages/cops_lint.adoc @@ -779,6 +779,34 @@ end alias bar foo ---- +== Lint/DuplicateRequire + +|=== +| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged + +| Pending +| Yes +| No +| 0.90 +| - +|=== + +This cop checks for duplicate `require`s and `require_relative`s. + +=== Examples + +[source,ruby] +---- +# bad +require 'foo' +require 'bar' +require 'foo' + +# good +require 'foo' +require 'bar' +---- + == Lint/DuplicateRescueException |=== diff --git a/lib/rubocop.rb b/lib/rubocop.rb index e96e16eb34f..ddc53c6ee0a 100644 --- a/lib/rubocop.rb +++ b/lib/rubocop.rb @@ -255,6 +255,7 @@ require_relative 'rubocop/cop/lint/duplicate_elsif_condition' require_relative 'rubocop/cop/lint/duplicate_hash_key' require_relative 'rubocop/cop/lint/duplicate_methods' +require_relative 'rubocop/cop/lint/duplicate_require' require_relative 'rubocop/cop/lint/duplicate_rescue_exception' require_relative 'rubocop/cop/lint/each_with_object_argument' require_relative 'rubocop/cop/lint/else_layout' diff --git a/lib/rubocop/cop/lint/duplicate_require.rb b/lib/rubocop/cop/lint/duplicate_require.rb new file mode 100644 index 00000000000..81f13998a2e --- /dev/null +++ b/lib/rubocop/cop/lint/duplicate_require.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Lint + # This cop checks for duplicate `require`s and `require_relative`s. + # + # @example + # # bad + # require 'foo' + # require 'bar' + # require 'foo' + # + # # good + # require 'foo' + # require 'bar' + # + class DuplicateRequire < Base + MSG = 'Duplicate `%s` detected.' + REQUIRE_METHODS = %i[require require_relative].freeze + + def_node_matcher :require_call?, <<~PATTERN + (send {nil? (const _ :Kernel)} {:#{REQUIRE_METHODS.join(' :')}} _) + PATTERN + + def on_new_investigation + # Holds the known required files for a given parent node (used as key) + @required = Hash.new { |h, k| h[k] = Set.new }.compare_by_identity + super + end + + def on_send(node) + return unless REQUIRE_METHODS.include?(node.method_name) && require_call?(node) + return if @required[node.parent].add?(node.first_argument) + + add_offense(node, message: format(MSG, method: node.method_name)) + end + end + end + end +end diff --git a/spec/rubocop/cop/lint/duplicate_require_spec.rb b/spec/rubocop/cop/lint/duplicate_require_spec.rb new file mode 100644 index 00000000000..8767d4c5b25 --- /dev/null +++ b/spec/rubocop/cop/lint/duplicate_require_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Lint::DuplicateRequire do + subject(:cop) { described_class.new } + + it 'registers an offense when duplicate `require` is detected' do + expect_offense(<<~RUBY) + require 'foo' + require 'foo' + ^^^^^^^^^^^^^ Duplicate `require` detected. + RUBY + end + + it 'registers an offense when duplicate `require_relative` is detected' do + expect_offense(<<~RUBY) + require_relative '../bar' + require_relative '../bar' + ^^^^^^^^^^^^^^^^^^^^^^^^^ Duplicate `require_relative` detected. + RUBY + end + + it 'registers an offense when duplicate `require` through `Kernel` is detected' do + expect_offense(<<~RUBY) + require 'foo' + Kernel.require 'foo' + ^^^^^^^^^^^^^^^^^^^^ Duplicate `require` detected. + RUBY + end + + it 'registers an offense for multiple duplicate requires' do + expect_offense(<<~RUBY) + require 'foo' + require_relative '../bar' + require 'foo/baz' + require_relative '../bar' + ^^^^^^^^^^^^^^^^^^^^^^^^^ Duplicate `require_relative` detected. + Kernel.require 'foo' + ^^^^^^^^^^^^^^^^^^^^ Duplicate `require` detected. + require 'quux' + RUBY + end + + it 'registers an offense when duplicate requires are interleaved with some other code' do + expect_offense(<<~RUBY) + require 'foo' + def m + end + require 'foo' + ^^^^^^^^^^^^^ Duplicate `require` detected. + RUBY + end + + it 'registers an offense for duplicate non top-level requires' do + expect_offense(<<~RUBY) + def m + require 'foo' + require 'foo' + ^^^^^^^^^^^^^ Duplicate `require` detected. + end + RUBY + end + + it 'does not register an offense when there are no duplicate `require`s' do + expect_no_offenses(<<~RUBY) + require 'foo' + require 'bar' + RUBY + end + + it 'does not register an offense when using single `require`' do + expect_no_offenses(<<~RUBY) + require 'foo' + RUBY + end + + it 'does not register an offense when calling user-defined `require` method' do + expect_no_offenses(<<~RUBY) + params.require(:user) + params.require(:user) + RUBY + end +end