Skip to content

Commit

Permalink
PrismScanner: Support more node types
Browse files Browse the repository at this point in the history
  • Loading branch information
davidwessman authored and glebm committed Feb 9, 2025
1 parent a0e2fef commit 948ac9c
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 27 deletions.
89 changes: 77 additions & 12 deletions lib/i18n/tasks/scanners/prism_scanners/nodes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ def name
@node.name.to_s
end

def receiver
@node.receiver
end

def type
:def_node
end
Expand All @@ -220,26 +224,41 @@ def translation_nodes(path: nil, options: nil)
translation_nodes_from_calls(path: local_path, options: options)
end

def translation_nodes_from_calls(path: nil, options: nil)
def translation_nodes_from_calls(path: nil, options: nil) # rubocop:disable Metrics/MethodLength
options ||= {}
other_def_nodes = options[:def_nodes] || []
@calls
.filter_map do |call|
next if call.nil?

case call.type
when :translation_node
call.with_context(path: path, options: options)
else
when :call_node
other_method =
other_def_nodes&.find { |m| m.name.to_s == call.name.to_s }
other_def_nodes&.find do |m|
m.name.to_s == call.name.to_s && m.receiver == call.receiver
end
next if other_method.nil?

other_method.add_call_from(@node.name.to_s)
other_method.translation_nodes(path: path, options: options)
else
if call.respond_to?(:translation_nodes)
call.translation_nodes(path: path, options: options)
else
puts(
"Cannot handle calls with: #{call.type},
if it can contain translations please add it to the case statement."
)
end
end
end
.flatten(1)
end

def before_action_translation_nodes(path: nil, options: nil)
options ||= {}
before_actions = options[:before_actions]
return [] if private_method || before_actions.nil?

Expand Down Expand Up @@ -393,13 +412,13 @@ def support_relative_keys?(context_path)
class BeforeActionNode < BaseNode
attr_reader(:name)

def initialize(node:, only:, except:, name: nil, translation_nodes: nil)
def initialize(node:, only:, except:, name: nil, calls: nil)
@node = node
@name = name
@only = only.present? ? Array(only).map(&:to_s) : nil
@except = except.present? ? Array(except).map(&:to_s) : nil
@translation_nodes = translation_nodes
@method = nil
@calls = calls

super(node: node)
end
Expand All @@ -413,7 +432,11 @@ def type
end

def calls
@method&.calls || []
if @method.present?
@method.calls
else
@calls || []
end
end

def add_method(method)
Expand All @@ -436,14 +459,12 @@ def applies_to?(method_name)
end

def translation_nodes(path: nil, options: nil)
if @translation_nodes.present?
@translation_nodes.flat_map do |child_node|
child_node.with_context(path: path, options: options)
end
elsif @method.present?
if @method.present?
@method.translation_nodes(path: path, options: options)
else
[]
(@calls || []).filter_map do |call|
call.with_context(path: path, options: options) if call.type == :translation_node
end
end
end
end
Expand Down Expand Up @@ -533,6 +554,10 @@ def name
@node.name
end

def receiver
@node.receiver
end

def translation_nodes(path: nil, options: nil)
options ||= {}
@comment_translations.map do |child_node|
Expand All @@ -546,4 +571,44 @@ def translation_nodes(path: nil, options: nil)
end
end
end

class LambdaNode < BaseNode
attr_reader(:calls)

def initialize(node:, calls:)
@node = node
@calls = calls
super(node: node)
end

def type
:lambda_node
end

def translation_nodes(path: nil, options: nil)
@calls.filter_map do |call|
call.with_context(path: path, options: options) if call.type == :translation_node
end
end
end

class BlockNode < BaseNode
attr_reader(:calls)

def initialize(node:, calls:)
@node = node
@calls = calls
super(node: node)
end

def type
:block_node
end

def translation_nodes(path: nil, options: nil)
@calls.filter_map do |call|
call.with_context(path: path, options: options) if call.type == :translation_node
end
end
end
end
20 changes: 15 additions & 5 deletions lib/i18n/tasks/scanners/prism_scanners/rails_visitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ def visit_class_node(node)

node
.body
.body
.map { |n| visit(n) }
.each { |child_node| class_object.add_child_node(child_node) }
&.body
&.map { |n| visit(n) }
&.each { |child_node| class_object.add_child_node(child_node) }

class_object
end
Expand Down Expand Up @@ -71,6 +71,16 @@ def handle_translation_call(node, comment_translations)

def handle_before_action(node) # rubocop:disable Metrics/MethodLength
array_arguments, keywords = process_arguments(node)

if array_arguments.empty? && node.block.present?
return BeforeActionNode.new(
node: node,
calls: visit(node.block).calls,
only: keywords['only'],
except: keywords['except']
)
end

if array_arguments.empty? || array_arguments.size > 2
fail(
ArgumentError,
Expand All @@ -86,10 +96,10 @@ def handle_before_action(node) # rubocop:disable Metrics/MethodLength
only: keywords['only'],
except: keywords['except']
)
elsif first_argument.is_a?(Prism::StatementsNode)
elsif first_argument.is_a?(LambdaNode)
BeforeActionNode.new(
node: node,
translation_nodes: visit(first_argument),
calls: first_argument.calls,
only: keywords['only'],
except: keywords['except']
)
Expand Down
46 changes: 42 additions & 4 deletions lib/i18n/tasks/scanners/prism_scanners/visitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# Any Rails-specific processing is added in the RailsVisitor class.

module I18n::Tasks::Scanners::PrismScanners
class Visitor < Prism::Visitor
class Visitor < Prism::Visitor # rubocop:disable Metrics/ClassLength
def initialize(comments: nil)
@private_methods = false
@comment_translations_by_row = prepare_comments_by_line(comments)
Expand Down Expand Up @@ -52,11 +52,11 @@ def prepare_comments_by_line(comments)
end

def visit_statements_node(node)
node.body.map { |child| visit(child) }
node.body.map { |child| child.accept(self) }
end

def visit_program_node(node)
node.statements.body.map { |child| child.accept(self) }
node.statements&.body&.map { |child| child.accept(self) }
end

def visit_module_node(node)
Expand All @@ -78,12 +78,50 @@ def visit_class_node(node)
class_object
end

def visit_instance_variable_write_node(node)
node.child_nodes.map { |n| visit(n) }
end

def visit_local_variable_write_node(node)
node.child_nodes.map { |n| visit(n) }
end

def visit_def_node(node)
calls = node.body.child_nodes.filter_map { |n| visit(n) }
calls = node.body.child_nodes.filter_map { |n| visit(n) }.flatten

DefNode.new(node: node, calls: calls, private_method: @private_methods)
end

def visit_if_node(node)
node.child_nodes.compact.map { |n| visit(n) }
end

def visit_else_node(node)
visit(node.statements)
end

def visit_elsif_node(node)
visit_if_node(node)
end

def visit_and_node(node)
[visit(node.left), visit(node.right)]
end

def visit_or_node(node)
[visit(node.left), visit(node.right)]
end

def visit_lambda_node(node)
LambdaNode.new(node: node, calls: node.body.child_nodes.map { |n| visit(n) })
end

def visit_block_node(node)
calls = node.body.child_nodes.filter_map { |n| visit(n) }.flatten

BlockNode.new(node: node, calls: calls)
end

def visit_call_node(node)
# TODO: How to handle multiple comments for same row?
comment_translations =
Expand Down
30 changes: 24 additions & 6 deletions spec/prism_scanner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ class EventsController < ApplicationController
before_action('method_in_before_action2', except: %i[create])
def create
t('.relative_key')
t('absolute_key')
I18n.t('very_absolute_key')
I18n.t('.other_relative_key')
value = t('.relative_key')
@key = t('absolute_key')
some_method || I18n.t('very_absolute_key') && other
-> { I18n.t('.other_relative_key') }
method_a
end
def custom_action
t('.relative_key')
value = if this
t('.relative_key')
else
I18n.t('absolute_key')
end
method_a
end
Expand Down Expand Up @@ -57,10 +61,24 @@ def method_in_before_action2
)
end

it 'empty controller' do
source = <<~RUBY
class ApplicationController < ActionController::Base
end
RUBY
expect(
process_string('app/controllers/application_controller.rb', source)
).to be_empty
end

it 'handles before_action as lambda' do
source = <<~RUBY
class EventsController < ApplicationController
before_action -> { t('.before_action') }, only: :create
before_action { non_existent if what? }
before_action do
t('.before_action2')
end
def create
t('.relative_key')
Expand All @@ -72,7 +90,7 @@ def create
process_string('app/controllers/events_controller.rb', source)

expect(occurrences.map(&:first).uniq).to match_array(
%w[events.create.relative_key events.create.before_action]
%w[events.create.relative_key events.create.before_action events.create.before_action2]
)
end

Expand Down

0 comments on commit 948ac9c

Please sign in to comment.