Skip to content

Commit

Permalink
Adds AST-matcher for Rails models
Browse files Browse the repository at this point in the history
- Adds a class for matching Rails model translations.
- User.human_attribute_name('name') and User.model_name.human(count: 2)
  • Loading branch information
davidwessman committed Apr 2, 2022
1 parent 1c3bb63 commit 22e1d38
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 23 deletions.
63 changes: 63 additions & 0 deletions lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# frozen_string_literal: true

require 'i18n/tasks/scanners/results/occurrence'

module I18n::Tasks::Scanners::AstMatchers
class RailsModelMatcher < BaseMatcher
def convert_to_key_occurrences(send_node, _method_name, location: send_node.loc)
human_attribute_name_to_key_occurences(send_node: send_node, location: location) ||
model_name_human_to_key_occurences(send_node: send_node, location: location)
end

private

def human_attribute_name_to_key_occurences(send_node:, location:)
children = Array(send_node&.children)
receiver = children[0]
method_name = children[1]

return unless method_name == :human_attribute_name && receiver.type == :const

value = children[2]

model_name = receiver.to_a.last.downcase
attribute = extract_string(value)
key = "activerecord.attributes.#{model_name}.#{attribute}"
[
key,
I18n::Tasks::Scanners::Results::Occurrence.from_range(
raw_key: key,
range: location.expression
)
]
end

# User.model_name.human(count: 2)
# s(:send,
# s(:send,
# s(:const, nil, :User), :model_name), :human,
# s(:hash,
# s(:pair,
# s(:sym, :count),
# s(:int, 2))))
def model_name_human_to_key_occurences(send_node:, location:)
children = Array(send_node&.children)
return unless children[1] == :human

base_children = Array(children[0]&.children)
class_node = base_children[0]

return unless class_node&.type == :const && base_children[1] == :model_name

model_name = class_node.to_a.last.downcase
key = "activerecord.models.#{model_name}.other"
[
key,
I18n::Tasks::Scanners::Results::Occurrence.from_range(
raw_key: key,
range: location.expression
)
]
end
end
end
3 changes: 2 additions & 1 deletion lib/i18n/tasks/scanners/ruby_ast_scanner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require 'i18n/tasks/scanners/relative_keys'
require 'i18n/tasks/scanners/ruby_ast_call_finder'
require 'i18n/tasks/scanners/ast_matchers/message_receivers_matcher'
require 'i18n/tasks/scanners/ast_matchers/rails_model_matcher'
require 'parser/current'

module I18n::Tasks::Scanners
Expand Down Expand Up @@ -131,7 +132,7 @@ def setup_matchers
message: message,
scanner: self
)
end
end << AstMatchers::RailsModelMatcher.new(scanner: self)
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions spec/fixtures/used_keys/a.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ def self.whot
Service.translate(:what)
I18n.t('activerecord.attributes.absolute.attribute')
translate('activerecord.attributes.absolute.attribute')
Archive.human_attribute_name(:name)
User.model_name.human(count: 2)
end
end
6 changes: 4 additions & 2 deletions spec/fixtures/used_keys/app/views/application/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
<% what = t 'a' %>
I18n.t("this_should_not")
<h2>
<% # i18n-tasks-use t('activerecord.models.first.one') %>
<%= First.model_name.human(count: 1) %>
<% # i18n-tasks-use t('comment.absolute.attribute') %>
<%= Translate.absolute.attribute %>
<%= Meeting.model_name.human(count: 1) %>
<%= Agenda.human_attribute_name(:title) %>
</h2>
<h3>
<%= t('with_parameter', parameter: "erb is the best") %>
Expand Down
73 changes: 54 additions & 19 deletions spec/used_keys_erb_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
used_keys = task.used_tree
expect(used_keys.size).to eq(1)
leaves = used_keys.leaves.to_a
expect(leaves.size).to eq(7)
expect(leaves.size).to eq(9)

expect_node_key_data(
leaves[0],
Expand All @@ -41,30 +41,65 @@
]
)
)

expect_node_key_data(
leaves[1],
'activerecord.models.meeting.other',
occurrences: make_occurrences(
[
{
path: 'app/views/application/show.html.erb',
pos: 184,
line_num: 7, line_pos: 5,
line: " <%= Meeting.model_name.human(count: 1) %>",
raw_key: 'activerecord.models.meeting.other'
},
]
)
)

expect_node_key_data(
leaves[2],
'activerecord.attributes.agenda.title',
occurrences: make_occurrences(
[
{
path: 'app/views/application/show.html.erb',
pos: 228,
line_num: 8, line_pos: 5,
line: " <%= Agenda.human_attribute_name(:title) %>",
raw_key: 'activerecord.attributes.agenda.title'
},
]
)
)


expect_node_key_data(
leaves[3],
'with_parameter',
occurrences: make_occurrences(
[
{
path: 'app/views/application/show.html.erb',
pos: 202,
line_num: 9, line_pos: 5,
pos: 284,
line_num: 11, line_pos: 5,
line: " <%= t('with_parameter', parameter: \"erb is the best\") %>",
raw_key: 'with_parameter'
}
]
)
)

expect_node_key_data(
leaves[2],
leaves[4],
'scope_a.scope_b.with_scope',
occurrences: make_occurrences(
[
{
path: 'app/views/application/show.html.erb',
pos: 261,
line_num: 10, line_pos: 5,
pos: 343,
line_num: 12, line_pos: 5,
line: " <%= t 'with_scope', scope: \"scope_a.scope_b\", default: t(\".nested_call\") %>",
raw_key: 'scope_a.scope_b.with_scope'
}
Expand All @@ -73,14 +108,14 @@
)

expect_node_key_data(
leaves[3],
leaves[5],
'application.show.nested_call',
occurrences: make_occurrences(
[
{
path: 'app/views/application/show.html.erb',
pos: 313,
line_num: 10, line_pos: 57,
pos: 395,
line_num: 12, line_pos: 57,
line: " <%= t 'with_scope', scope: \"scope_a.scope_b\", default: t(\".nested_call\") %>",
raw_key: '.nested_call'
}
Expand All @@ -89,14 +124,14 @@
)

expect_node_key_data(
leaves[4],
leaves[6],
"application.show.edit",
occurrences: make_occurrences(
[
{
path: 'app/views/application/show.html.erb',
pos: 433,
line_num: 13, line_pos: 41,
pos: 515,
line_num: 15, line_pos: 41,
line: ' <%= link_to(edit_foo_path(foo), title: t(".edit")) do %>',
raw_key: '.edit'
}
Expand All @@ -105,14 +140,14 @@
)

expect_node_key_data(
leaves[5],
leaves[7],
"blacklight.tools.citation",
occurrences: make_occurrences(
[
{
path: 'app/views/application/show.html.erb',
pos: 655,
line_num: 19, line_pos: 25,
pos: 737,
line_num: 21, line_pos: 25,
line: " <% component.title { t('blacklight.tools.citation') } %>",
raw_key: 'blacklight.tools.citation'
}
Expand All @@ -121,16 +156,16 @@
)

expect_node_key_data(
leaves[6],
'activerecord.models.first.one',
leaves[8],
'comment.absolute.attribute',
occurrences: make_occurrences(
[
{
path: 'app/views/application/show.html.erb',
pos: 88,
line_num: 5, line_pos: 4,
line: " <% # i18n-tasks-use t('activerecord.models.first.one') %>",
raw_key: 'activerecord.models.first.one'
line: " <% # i18n-tasks-use t('comment.absolute.attribute') %>",
raw_key: 'comment.absolute.attribute'
}
]
)
Expand Down
32 changes: 31 additions & 1 deletion spec/used_keys_ruby_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
used_keys = task.used_tree
expect(used_keys.size).to eq(1)
leaves = used_keys.leaves.to_a
expect(leaves.size).to(eq(3))
expect(leaves.size).to(eq(5))

expect_node_key_data(
leaves[0],
Expand Down Expand Up @@ -53,6 +53,36 @@

expect_node_key_data(
leaves[2],
'activerecord.attributes.archive.name',
occurrences: make_occurrences(
[
{
path: 'a.rb', pos: 276,
line_num: 15, line_pos: 4,
line: " Archive.human_attribute_name(:name)",
raw_key: 'activerecord.attributes.archive.name'
}
]
)
)

expect_node_key_data(
leaves[3],
'activerecord.models.user.other',
occurrences: make_occurrences(
[
{
path: 'a.rb', pos: 316,
line_num: 16, line_pos: 4,
line: " User.model_name.human(count: 2)",
raw_key: 'activerecord.models.user.other'
}
]
)
)

expect_node_key_data(
leaves[4],
'service.what',
occurrences: make_occurrences(
[
Expand Down

0 comments on commit 22e1d38

Please sign in to comment.