Skip to content

Commit

Permalink
Merge pull request #326 from fatkodima/i18n_lazy_lookup-cop
Browse files Browse the repository at this point in the history
Add new `Rails/I18nLazyLookup` cop
  • Loading branch information
koic authored Jan 25, 2022
2 parents ea7a891 + eed8c42 commit c4116f4
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog/new_add_new_i18n_lazy_lookup_cop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#326](https://github.com/rubocop/rubocop-rails/pull/326): Add new `Rails/I18nLazyLookup` cop. ([@fatkodima][])
9 changes: 9 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,15 @@ Rails/HttpStatus:
- numeric
- symbolic

Rails/I18nLazyLookup:
Description: 'Checks for places where I18n "lazy" lookup can be used.'
StyleGuide: 'https://rails.rubystyle.guide/#lazy-lookup'
Reference: 'https://guides.rubyonrails.org/i18n.html#lazy-lookup'
Enabled: pending
VersionAdded: '<<next>>'
Include:
- 'controllers/**/*'

Rails/I18nLocaleAssignment:
Description: 'Prefer the usage of `I18n.with_locale` instead of manually updating `I18n.locale` value.'
Enabled: 'pending'
Expand Down
94 changes: 94 additions & 0 deletions lib/rubocop/cop/rails/i18n_lazy_lookup.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Rails
# This cop checks for places where I18n "lazy" lookup can be used.
#
# @example
# # en.yml
# # en:
# # books:
# # create:
# # success: Book created!
#
# # bad
# class BooksController < ApplicationController
# def create
# # ...
# redirect_to books_url, notice: t('books.create.success')
# end
# end
#
# # good
# class BooksController < ApplicationController
# def create
# # ...
# redirect_to books_url, notice: t('.success')
# end
# end
#
class I18nLazyLookup < Base
include VisibilityHelp
extend AutoCorrector

MSG = 'Use "lazy" lookup for the text used in controllers.'

def_node_matcher :translate_call?, <<~PATTERN
(send nil? {:translate :t} ${sym_type? str_type?} ...)
PATTERN

def on_send(node)
translate_call?(node) do |key_node|
key = key_node.value
return if key.to_s.start_with?('.')

controller, action = controller_and_action(node)
return unless controller && action

scoped_key = get_scoped_key(key_node, controller, action)
return unless key == scoped_key

add_offense(key_node) do |corrector|
unscoped_key = key_node.value.to_s.split('.').last
corrector.replace(key_node, "'.#{unscoped_key}'")
end
end
end

private

def controller_and_action(node)
action_node = node.each_ancestor(:def).first
return unless action_node && node_visibility(action_node) == :public

controller_node = node.each_ancestor(:class).first
return unless controller_node && controller_node.identifier.source.end_with?('Controller')

[controller_node, action_node]
end

def get_scoped_key(key_node, controller, action)
path = controller_path(controller).tr('/', '.')
action_name = action.method_name
key = key_node.value.to_s.split('.').last

"#{path}.#{action_name}.#{key}"
end

def controller_path(controller)
module_name = controller.parent_module_name
controller_name = controller.identifier.source

path = if module_name == 'Object'
controller_name
else
"#{module_name}::#{controller_name}"
end

path.delete_suffix('Controller').underscore
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/rails_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
require_relative 'rails/helper_instance_variable'
require_relative 'rails/http_positional_arguments'
require_relative 'rails/http_status'
require_relative 'rails/i18n_lazy_lookup'
require_relative 'rails/i18n_locale_assignment'
require_relative 'rails/ignored_skip_action_filter_option'
require_relative 'rails/index_by'
Expand Down
114 changes: 114 additions & 0 deletions spec/rubocop/cop/rails/i18n_lazy_lookup_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Rails::I18nLazyLookup, :config do
it 'registers an offense and corrects when using translation helpers with the key scoped to controller and action' do
expect_offense(<<~RUBY)
class FooController
def action
t 'foo.action.key'
^^^^^^^^^^^^^^^^ Use "lazy" lookup for the text used in controllers.
translate 'foo.action.key'
^^^^^^^^^^^^^^^^ Use "lazy" lookup for the text used in controllers.
end
end
RUBY

expect_correction(<<~RUBY)
class FooController
def action
t '.key'
translate '.key'
end
end
RUBY
end

it 'does not register an offense when translation methods scoped to `I18n`' do
expect_no_offenses(<<~RUBY)
class FooController
def action
I18n.t 'foo.action.key'
I18n.translate 'foo.action.key'
end
end
RUBY
end

it 'does not register an offense when not inside controller' do
expect_no_offenses(<<~RUBY)
class FooService
def do_something
t 'foo_service.do_something.key'
end
end
RUBY
end

it 'does not register an offense when not inside controller action' do
expect_no_offenses(<<~RUBY)
class FooController
private
def action
t 'foo.action.key'
end
end
RUBY
end

it 'does not register an offense when translating key not scoped to controller and action' do
expect_no_offenses(<<~RUBY)
class FooController
def action
t 'one.two.key'
end
end
RUBY
end

it 'does not register an offense when using "lazy" translation' do
expect_no_offenses(<<~RUBY)
class FooController
def action
t '.key'
end
end
RUBY
end

it 'does not register an offense when translation key is not a string nor a symbol' do
expect_no_offenses(<<~RUBY)
class FooController
def action
t ['foo.action.key']
t key
end
end
RUBY
end

it 'handles scoped controllers' do
expect_offense(<<~RUBY)
module Bar
class FooController
def action
t 'bar.foo.action.key'
^^^^^^^^^^^^^^^^^^^^ Use "lazy" lookup for the text used in controllers.
t 'foo.action.key'
end
end
end
RUBY

expect_correction(<<~RUBY)
module Bar
class FooController
def action
t '.key'
t 'foo.action.key'
end
end
end
RUBY
end
end

0 comments on commit c4116f4

Please sign in to comment.