Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## master (unreleased)

### New features

* [#64](https://github.com/rubocop/rubocop-rake/pull/64): Add `Rake/RequireOutsideTask` cop. ([@corsonknowles][])

### Changes

* [#66](https://github.com/rubocop/rubocop-rake/pull/66): Speed up loading rubocop-rake by lazily loading only the cops needed for a run. This requires RuboCop 1.89.0+. ([@koic][])
Expand Down Expand Up @@ -82,3 +86,4 @@
[@jaruuuu]: https://github.com/jaruuuu
[@koic]: https://github.com/koic
[@tejasbubane]: https://github.com/tejasbubane
[@corsonknowles]: https://github.com/corsonknowles
7 changes: 7 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,10 @@ Rake/MethodDefinitionInTask:
Description: 'Do not define a method in rake task, because it will be defined to the top level.'
Enabled: true
VersionAdded: '0.2.0'

Rake/RequireOutsideTask:
Description: 'Do not `require` outside of a task block, because it is loaded every time the rake file is loaded.'
Enabled: pending
VersionAdded: '<<next>>'
Exclude:
- 'Rakefile'
1 change: 1 addition & 0 deletions lib/rubocop/cop/rake.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module Rake
register_cop :DuplicateTask, "#{__dir__}/rake/duplicate_task"
register_cop :DuplicateNamespace, "#{__dir__}/rake/duplicate_namespace"
register_cop :MethodDefinitionInTask, "#{__dir__}/rake/method_definition_in_task"
register_cop :RequireOutsideTask, "#{__dir__}/rake/require_outside_task"
end
end
end
76 changes: 76 additions & 0 deletions lib/rubocop/cop/rake/require_outside_task.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Rake
# Detects `require` and `require_relative` calls that are not inside a
# task block.
#
# A rake file's top level - including the body of a `namespace` block -
# is evaluated every time the file is loaded. A require placed there
# therefore runs on every rake invocation, instead of only when the task
# that needs it runs. Move the require inside the task (or a
# `Rake::Task#enhance` block) so the dependency is loaded lazily.
#
# @example
# # bad
# require 'foo'
#
# task :foo do
# Foo.do_something
# end
#
# # bad - the namespace body is still evaluated on load
# namespace :foo do
# require 'foo'
#
# task :bar do
# Foo.do_something
# end
# end
#
# # good
# task :foo do
# require 'foo'
# Foo.do_something
# end
#
# # good
# Rake::Task['db:seed'].enhance do
# require 'foo'
# Foo.do_something
# end
#
class RequireOutsideTask < Base
MSG = 'Do not `require` outside of a task block, because it is loaded every time the rake file is loaded.'
RESTRICT_ON_SEND = %i[require require_relative].freeze

# @!method task_block?(node)
def_node_matcher :task_block?, <<~PATTERN
(block (send nil? :task ...) ...)
PATTERN

# @!method task_enhance_block?(node)
def_node_matcher :task_enhance_block?, <<~PATTERN
(block
(send
(send (const (const nil? :Rake) :Task) :[] ...)
:enhance ...)
...)
PATTERN

def on_send(node)
return if inside_task_block?(node)

add_offense(node)
end

private def inside_task_block?(node)
node.each_ancestor(:block).any? do |ancestor|
task_block?(ancestor) || task_enhance_block?(ancestor)
end
end
end
end
end
end
2 changes: 1 addition & 1 deletion spec/lazy_loading_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def run_script(source)
puts "loaded_cop_files=\#{loaded.size}"
RUBY

expect(output).to include('registered=5', 'loaded_cop_files=0')
expect(output).to include('registered=6', 'loaded_cop_files=0')
end

it 'resolves every helper file in `lib/rubocop/cop/rake/helper` through an autoload' do
Expand Down
55 changes: 55 additions & 0 deletions spec/rubocop/cop/rake/require_outside_task_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Rake::RequireOutsideTask, :config do
it 'registers an offense for `require` at the top level' do
expect_offense(<<~RUBY)
require 'foo'
^^^^^^^^^^^^^ Do not `require` outside of a task block, because it is loaded every time the rake file is loaded.

task :foo do
Foo.do_something
end
RUBY
end

it 'registers an offense for `require_relative` at the top level' do
expect_offense(<<~RUBY)
require_relative 'foo'
^^^^^^^^^^^^^^^^^^^^^^ Do not `require` outside of a task block, because it is loaded every time the rake file is loaded.

task :foo do
end
RUBY
end

it 'registers an offense for `require` inside a namespace but outside a task' do
expect_offense(<<~RUBY)
namespace :foo do
require 'foo'
^^^^^^^^^^^^^ Do not `require` outside of a task block, because it is loaded every time the rake file is loaded.

task :bar do
Foo.do_something
end
end
RUBY
end

it 'does not register an offense for `require` inside a task' do
expect_no_offenses(<<~RUBY)
task :foo do
require 'foo'
Foo.do_something
end
RUBY
end

it 'does not register an offense for `require` inside a `Rake::Task#enhance` block' do
expect_no_offenses(<<~RUBY)
Rake::Task['db:seed'].enhance do
require 'foo'
Foo.do_something
end
RUBY
end
end