Skip to content

Commit

Permalink
Add new Lint/DuplicateRequire cop
Browse files Browse the repository at this point in the history
  • Loading branch information
fatkodima committed Aug 12, 2020
1 parent 6d96917 commit 10e4a7c
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 0 deletions.
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

0 comments on commit 10e4a7c

Please sign in to comment.