Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new Lint/DuplicateRequire cop #8474

Merged
merged 1 commit into from Aug 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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][])
Expand Down
5 changes: 5 additions & 0 deletions config/default.yml
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Expand Up @@ -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]
Expand Down
28 changes: 28 additions & 0 deletions docs/modules/ROOT/pages/cops_lint.adoc
Expand Up @@ -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

|===
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Expand Up @@ -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'
Expand Down
41 changes: 41 additions & 0 deletions 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 `%<method>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
82 changes: 82 additions & 0 deletions 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