Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
150 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* [#325](https://github.com/rubocop/rubocop-rails/pull/325): Add new `Rails/DotSeparatedKeys` cop. ([@fatkodima][]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module Rails | ||
# Enforces the use of dot-separated locale keys instead of specifying the `:scope` option | ||
# with an array or a single symbol in `I18n` translation methods. | ||
# Dot-separated notation is easier to read and trace the hierarchy. | ||
# | ||
# @example | ||
# # bad | ||
# I18n.t :record_invalid, scope: [:activerecord, :errors, :messages] | ||
# I18n.t :title, scope: :invitation | ||
# | ||
# # good | ||
# I18n.t 'activerecord.errors.messages.record_invalid' | ||
# I18n.t :record_invalid, scope: 'activerecord.errors.messages' | ||
# | ||
class DotSeparatedKeys < Base | ||
include RangeHelp | ||
extend AutoCorrector | ||
|
||
MSG = 'Use the dot-separated keys instead of specifying the `:scope` option.' | ||
TRANSLATE_METHODS = %i[translate t].freeze | ||
|
||
def_node_matcher :translate_with_scope?, <<~PATTERN | ||
(send {nil? (const nil? :I18n)} {:translate :t} ${sym_type? str_type?} | ||
(hash <$(pair (sym :scope) ${array_type? sym_type?}) ...>) | ||
) | ||
PATTERN | ||
|
||
def on_send(node) | ||
return unless TRANSLATE_METHODS.include?(node.method_name) | ||
|
||
translate_with_scope?(node) do |key_node, scope_node| | ||
return unless should_convert_scope?(scope_node) | ||
|
||
add_offense(scope_node) do |corrector| | ||
# Eat the comma on the left. | ||
range = range_with_surrounding_space(range: scope_node.source_range, side: :left) | ||
range = range_with_surrounding_comma(range, :left) | ||
corrector.remove(range) | ||
|
||
corrector.replace(key_node, new_key(key_node, scope_node)) | ||
end | ||
end | ||
end | ||
|
||
private | ||
|
||
def should_convert_scope?(scope_node) | ||
scopes(scope_node).all?(&:basic_literal?) | ||
end | ||
|
||
def new_key(key_node, scope_node) | ||
"'#{scopes(scope_node).map(&:value).join('.')}.#{key_node.value}'" | ||
end | ||
|
||
def scopes(scope_node) | ||
value = scope_node.value | ||
|
||
if value.array_type? | ||
value.values | ||
else | ||
[value] | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::Cop::Rails::DotSeparatedKeys, :config do | ||
it 'registers an offense and corrects when translating keys with convertible scopes' do | ||
expect_offense(<<~RUBY) | ||
I18n.t :key, scope: [:one, :two] | ||
^^^^^^^^^^^^^^^^^^^ Use the dot-separated keys instead of specifying the `:scope` option. | ||
I18n.translate :key, scope: [:one, :two] | ||
^^^^^^^^^^^^^^^^^^^ Use the dot-separated keys instead of specifying the `:scope` option. | ||
t :key, scope: [:one, :two] | ||
^^^^^^^^^^^^^^^^^^^ Use the dot-separated keys instead of specifying the `:scope` option. | ||
translate :key, scope: [:one, :two] | ||
^^^^^^^^^^^^^^^^^^^ Use the dot-separated keys instead of specifying the `:scope` option. | ||
t :key, scope: [:one, :two], default: 'Not here' | ||
^^^^^^^^^^^^^^^^^^^ Use the dot-separated keys instead of specifying the `:scope` option. | ||
I18n.t :key, scope: ['one', :two] | ||
^^^^^^^^^^^^^^^^^^^^ Use the dot-separated keys instead of specifying the `:scope` option. | ||
I18n.t 'key', scope: [:one, :two] | ||
^^^^^^^^^^^^^^^^^^^ Use the dot-separated keys instead of specifying the `:scope` option. | ||
I18n.t :key, scope: :one | ||
^^^^^^^^^^^ Use the dot-separated keys instead of specifying the `:scope` option. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
I18n.t 'one.two.key' | ||
I18n.translate 'one.two.key' | ||
t 'one.two.key' | ||
translate 'one.two.key' | ||
t 'one.two.key', default: 'Not here' | ||
I18n.t 'one.two.key' | ||
I18n.t 'one.two.key' | ||
I18n.t 'one.key' | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when key is an array' do | ||
expect_no_offenses(<<~RUBY) | ||
t [:key1, :key2], scope: :one | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when key is not a basic literal' do | ||
expect_no_offenses(<<~RUBY) | ||
t key1, scope: :one | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when `scope` is an array containing non literals' do | ||
expect_no_offenses(<<~RUBY) | ||
t :key, scope: [:one, two] | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when `scope` is a string' do | ||
expect_no_offenses(<<~RUBY) | ||
t :key, scope: 'one' | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when `scope` is not a literal' do | ||
expect_no_offenses(<<~RUBY) | ||
t :key, scope: something | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when there is no `scope`' do | ||
expect_no_offenses(<<~RUBY) | ||
t :key | ||
RUBY | ||
end | ||
end |