i18n-tasks 1.0.5 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/i18n/tasks/scanners/ast_matchers/base_matcher.rb +98 -0
- data/lib/i18n/tasks/scanners/ast_matchers/message_receivers_matcher.rb +86 -0
- data/lib/i18n/tasks/scanners/erb_ast_processor.rb +16 -32
- data/lib/i18n/tasks/scanners/results/occurrence.rb +16 -0
- data/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb +8 -34
- data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +51 -147
- data/lib/i18n/tasks/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d1d2fc604c929450bdf96a547513004630f57a1da7e368020b0313f4e4c67f4
|
4
|
+
data.tar.gz: 4b1a226b25f08c76d92998bc8cecd37de5e698f2f805da606804b8faa3fc0203
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aeff02b2b63a8d64a37e80342b4dcf41c1a2c0379a8792b1064c6af431e18350c3130b890ee8bb83b4cc0cc6527b0721dabd2c7eb96ea420414b749d7c1b7a35
|
7
|
+
data.tar.gz: ce61d0886ca762588e101032f2d51207742f24f30d5c8d681594ea44ae08f508ac5f032f6bad2e27b117bf349794089753cd9669aa9e25de5d72831ac5db2d63
|
data/README.md
CHANGED
@@ -24,7 +24,7 @@ i18n-tasks can be used with any project using the ruby [i18n gem][i18n-gem] (def
|
|
24
24
|
Add i18n-tasks to the Gemfile:
|
25
25
|
|
26
26
|
```ruby
|
27
|
-
gem 'i18n-tasks', '~> 1.0.
|
27
|
+
gem 'i18n-tasks', '~> 1.0.8'
|
28
28
|
```
|
29
29
|
|
30
30
|
Copy the default [configuration file](#configuration):
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module I18n::Tasks::Scanners::AstMatchers
|
4
|
+
class BaseMatcher
|
5
|
+
def initialize(scanner:)
|
6
|
+
@scanner = scanner
|
7
|
+
end
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
# If the node type is of `%i(sym str int false true)`, return the value as a string.
|
12
|
+
# Otherwise, if `config[:strict]` is `false` and the type is of `%i(dstr dsym)`,
|
13
|
+
# return the source as if it were a string.
|
14
|
+
#
|
15
|
+
# @param node [Parser::AST::Node]
|
16
|
+
# @param array_join_with [String, nil] if set to a string, arrays will be processed and their elements joined.
|
17
|
+
# @param array_flatten [Boolean] if true, nested arrays are flattened,
|
18
|
+
# otherwise their source is copied and surrounded by #{}. No effect unless `array_join_with` is set.
|
19
|
+
# @param array_reject_blank [Boolean] if true, empty strings and `nil`s are skipped.
|
20
|
+
# No effect unless `array_join_with` is set.
|
21
|
+
# @return [String, nil] `nil` is returned only when a dynamic value is encountered in strict mode
|
22
|
+
# or the node type is not supported.
|
23
|
+
def extract_string(node, array_join_with: nil, array_flatten: false, array_reject_blank: false)
|
24
|
+
return if node.nil?
|
25
|
+
|
26
|
+
if %i[sym str int].include?(node.type)
|
27
|
+
node.children[0].to_s
|
28
|
+
elsif %i[true false].include?(node.type)
|
29
|
+
node.type.to_s
|
30
|
+
elsif node.type == :nil
|
31
|
+
''
|
32
|
+
elsif node.type == :array && array_join_with
|
33
|
+
extract_array_as_string(
|
34
|
+
node,
|
35
|
+
array_join_with: array_join_with,
|
36
|
+
array_flatten: array_flatten,
|
37
|
+
array_reject_blank: array_reject_blank
|
38
|
+
).tap do |str|
|
39
|
+
# `nil` is returned when a dynamic value is encountered in strict mode. Propagate:
|
40
|
+
return nil if str.nil?
|
41
|
+
end
|
42
|
+
elsif !@scanner.config[:strict] && %i[dsym dstr].include?(node.type)
|
43
|
+
node.children.map do |child|
|
44
|
+
if %i[sym str].include?(child.type)
|
45
|
+
child.children[0].to_s
|
46
|
+
else
|
47
|
+
child.loc.expression.source
|
48
|
+
end
|
49
|
+
end.join
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Extract a hash pair with a given literal key.
|
54
|
+
#
|
55
|
+
# @param node [AST::Node] a node of type `:hash`.
|
56
|
+
# @param key [String] node key as a string (indifferent symbol-string matching).
|
57
|
+
# @return [AST::Node, nil] a node of type `:pair` or nil.
|
58
|
+
def extract_hash_pair(node, key)
|
59
|
+
node.children.detect do |child|
|
60
|
+
next unless child.type == :pair
|
61
|
+
|
62
|
+
key_node = child.children[0]
|
63
|
+
%i[sym str].include?(key_node.type) && key_node.children[0].to_s == key
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Extract an array as a single string.
|
68
|
+
#
|
69
|
+
# @param array_join_with [String] joiner of the array elements.
|
70
|
+
# @param array_flatten [Boolean] if true, nested arrays are flattened,
|
71
|
+
# otherwise their source is copied and surrounded by #{}.
|
72
|
+
# @param array_reject_blank [Boolean] if true, empty strings and `nil`s are skipped.
|
73
|
+
# @return [String, nil] `nil` is returned only when a dynamic value is encountered in strict mode.
|
74
|
+
def extract_array_as_string(node, array_join_with:, array_flatten: false, array_reject_blank: false)
|
75
|
+
children_strings = node.children.map do |child|
|
76
|
+
if %i[sym str int true false].include?(child.type)
|
77
|
+
extract_string child
|
78
|
+
else
|
79
|
+
# ignore dynamic argument in strict mode
|
80
|
+
return nil if @scanner.config[:strict]
|
81
|
+
|
82
|
+
if %i[dsym dstr].include?(child.type) || (child.type == :array && array_flatten)
|
83
|
+
extract_string(child, array_join_with: array_join_with)
|
84
|
+
else
|
85
|
+
"\#{#{child.loc.expression.source}}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
if array_reject_blank
|
90
|
+
children_strings.reject! do |x|
|
91
|
+
# empty strings and nils in the scope argument are ignored by i18n
|
92
|
+
x == ''
|
93
|
+
end
|
94
|
+
end
|
95
|
+
children_strings.join(array_join_with)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'i18n/tasks/scanners/ast_matchers/base_matcher'
|
4
|
+
require 'i18n/tasks/scanners/results/occurrence'
|
5
|
+
|
6
|
+
module I18n::Tasks::Scanners::AstMatchers
|
7
|
+
class MessageReceiversMatcher < BaseMatcher
|
8
|
+
def initialize(scanner:, receivers:, message:)
|
9
|
+
super(scanner: scanner)
|
10
|
+
@receivers = Array(receivers)
|
11
|
+
@message = message
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param send_node [Parser::AST::Node]
|
15
|
+
# @param method_name [Symbol, nil]
|
16
|
+
# @param location [Parser::Source::Map]
|
17
|
+
# @return [nil, [key, Occurrence]] full absolute key name and the occurrence.
|
18
|
+
def convert_to_key_occurrences(send_node, method_name, location: send_node.loc)
|
19
|
+
return unless node_match?(send_node)
|
20
|
+
|
21
|
+
receiver = send_node.children[0]
|
22
|
+
first_arg_node = send_node.children[2]
|
23
|
+
second_arg_node = send_node.children[3]
|
24
|
+
|
25
|
+
key = extract_string(first_arg_node)
|
26
|
+
return if key.nil?
|
27
|
+
|
28
|
+
key, default_arg = process_options(node: second_arg_node, key: key)
|
29
|
+
|
30
|
+
return if key.nil?
|
31
|
+
|
32
|
+
[
|
33
|
+
full_key(receiver: receiver, key: key, location: location, calling_method: method_name),
|
34
|
+
I18n::Tasks::Scanners::Results::Occurrence.from_range(
|
35
|
+
raw_key: key,
|
36
|
+
range: location.expression,
|
37
|
+
default_arg: default_arg
|
38
|
+
)
|
39
|
+
]
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def node_match?(node)
|
45
|
+
receiver = node.children[0]
|
46
|
+
message = node.children[1]
|
47
|
+
|
48
|
+
@message == message && @receivers.any? { |r| r == receiver }
|
49
|
+
end
|
50
|
+
|
51
|
+
def full_key(receiver:, key:, location:, calling_method:)
|
52
|
+
if receiver.nil?
|
53
|
+
# Relative keys only work if called via `t()` but not `I18n.t()`:
|
54
|
+
@scanner.absolute_key(
|
55
|
+
key,
|
56
|
+
location.expression.source_buffer.name,
|
57
|
+
calling_method: calling_method
|
58
|
+
)
|
59
|
+
else
|
60
|
+
key
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def process_options(node:, key:)
|
65
|
+
return [key, nil] if node&.type != :hash
|
66
|
+
|
67
|
+
scope_node = extract_hash_pair(node, 'scope')
|
68
|
+
|
69
|
+
if scope_node
|
70
|
+
scope = extract_string(
|
71
|
+
scope_node.children[1],
|
72
|
+
array_join_with: '.',
|
73
|
+
array_flatten: true,
|
74
|
+
array_reject_blank: true
|
75
|
+
)
|
76
|
+
return nil if scope.nil? && scope_node.type != :nil
|
77
|
+
|
78
|
+
key = [scope, key].join('.') unless scope == ''
|
79
|
+
end
|
80
|
+
default_arg_node = extract_hash_pair(node, 'default')
|
81
|
+
default_arg = extract_string(default_arg_node.children[1]) if default_arg_node
|
82
|
+
|
83
|
+
[key, default_arg]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -38,7 +38,9 @@ module I18n::Tasks::Scanners
|
|
38
38
|
# @param node [::Parser::AST::Node]
|
39
39
|
# @return [::Parser::AST::Node]
|
40
40
|
def handler_missing(node)
|
41
|
-
node =
|
41
|
+
node = handle_comment(node)
|
42
|
+
return if node.nil?
|
43
|
+
|
42
44
|
node.updated(
|
43
45
|
nil,
|
44
46
|
node.children.map { |child| node?(child) ? process(child) : child }
|
@@ -47,40 +49,22 @@ module I18n::Tasks::Scanners
|
|
47
49
|
|
48
50
|
private
|
49
51
|
|
50
|
-
#
|
51
|
-
# <%# ... #>
|
52
|
-
# (no space between % and #)
|
53
|
-
#
|
54
|
-
# With a space the AST is:
|
55
|
-
#
|
56
|
-
# s(:erb, nil, nil,
|
57
|
-
# s(:code, " # this should not fail: ' "), nil)
|
52
|
+
# Convert ERB-comments to ::Parser::Source::Comment and skip processing node
|
58
53
|
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
# @return [::Parser::AST::Node]
|
66
|
-
def transform_misparsed_comment(node)
|
67
|
-
return node unless node.type == :erb && node.children.size == 4 &&
|
68
|
-
node.children[0]&.type == :indicator && node.children[0].children[0] == "#" &&
|
69
|
-
node.children[1].nil? &&
|
70
|
-
node.children[2]&.type == :code &&
|
71
|
-
node.children[3].nil?
|
72
|
-
code_node = node.children[2]
|
54
|
+
# @param node Parser::AST::Node Potential comment node
|
55
|
+
# @return Parser::AST::Node or nil
|
56
|
+
def handle_comment(node)
|
57
|
+
if node.type == :erb && node.children.size == 4 &&
|
58
|
+
node.children[0]&.type == :indicator && node.children[0].children[0] == '#' &&
|
59
|
+
node.children[2]&.type == :code
|
73
60
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
61
|
+
# Do not continue parsing this node
|
62
|
+
comment = node.children[2]
|
63
|
+
@comments << ::Parser::Source::Comment.new(comment.location.expression)
|
64
|
+
return
|
65
|
+
end
|
79
66
|
|
80
|
-
node
|
81
|
-
nil,
|
82
|
-
[nil, nil, code_node.updated(nil, [code]), nil]
|
83
|
-
)
|
67
|
+
node
|
84
68
|
end
|
85
69
|
|
86
70
|
def node?(node)
|
@@ -63,6 +63,22 @@ module I18n::Tasks
|
|
63
63
|
def hash
|
64
64
|
[@path, @pos, @line_num, @line_pos, @line, @default_arg].hash
|
65
65
|
end
|
66
|
+
|
67
|
+
# @param raw_key [String]
|
68
|
+
# @param range [Parser::Source::Range]
|
69
|
+
# @param default_arg [String, nil]
|
70
|
+
# @return [Results::Occurrence]
|
71
|
+
def self.from_range(raw_key:, range:, default_arg: nil)
|
72
|
+
Occurrence.new(
|
73
|
+
path: range.source_buffer.name,
|
74
|
+
pos: range.begin_pos,
|
75
|
+
line_num: range.line,
|
76
|
+
line_pos: range.column,
|
77
|
+
line: range.source_line,
|
78
|
+
raw_key: raw_key,
|
79
|
+
default_arg: default_arg
|
80
|
+
)
|
81
|
+
end
|
66
82
|
end
|
67
83
|
end
|
68
84
|
end
|
@@ -1,57 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'ast'
|
4
|
-
require 'set'
|
5
4
|
|
6
5
|
module I18n::Tasks::Scanners
|
7
6
|
class RubyAstCallFinder
|
8
7
|
include AST::Processor::Mixin
|
9
8
|
|
10
|
-
# @param receiver_messages [Set<Pair<[nil, AST::Node>, Symbol>>] The receiver-message pairs to look for.
|
11
|
-
def initialize(receiver_messages:)
|
12
|
-
super()
|
13
|
-
@message_receivers = receiver_messages.each_with_object({}) do |(receiver, message), t|
|
14
|
-
(t[message] ||= []) << receiver
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
9
|
# @param root_node [Parser::AST:Node]
|
19
|
-
# @
|
20
|
-
# @yieldparam method_name [nil, String] the surrounding method's name.
|
21
|
-
def find_calls(root_node, &block)
|
22
|
-
@callback = block
|
23
|
-
process root_node
|
24
|
-
ensure
|
25
|
-
@callback = nil
|
26
|
-
end
|
27
|
-
|
28
|
-
# @param root_node (see #find_calls)
|
29
|
-
# @yieldparam (see #find_calls)
|
30
|
-
# @return [Array<block return values excluding nils>]
|
10
|
+
# @return [Pair<Parser::AST::Node, method_name as string>] for all nodes with :send type
|
31
11
|
def collect_calls(root_node)
|
32
|
-
results = []
|
33
|
-
|
34
|
-
|
35
|
-
results << result if result
|
36
|
-
end
|
37
|
-
results
|
12
|
+
@results = []
|
13
|
+
process(root_node)
|
14
|
+
@results
|
38
15
|
end
|
39
16
|
|
40
17
|
def on_def(node)
|
41
18
|
@method_name = node.children[0]
|
42
|
-
handler_missing
|
19
|
+
handler_missing(node)
|
43
20
|
ensure
|
44
21
|
@method_name = nil
|
45
22
|
end
|
46
23
|
|
47
24
|
def on_send(send_node)
|
48
|
-
|
49
|
-
|
50
|
-
valid_receivers = @message_receivers[message]
|
51
|
-
# use `any?` because `include?` checks type equality, but the receiver is a Parser::AST::Node != AST::Node.
|
52
|
-
@callback.call(send_node, @method_name) if valid_receivers&.any? { |r| r == receiver }
|
25
|
+
@results << [send_node, @method_name]
|
26
|
+
|
53
27
|
# always invoke handler_missing to get nested translations in children
|
54
|
-
handler_missing
|
28
|
+
handler_missing(send_node)
|
55
29
|
nil
|
56
30
|
end
|
57
31
|
|
@@ -3,27 +3,22 @@
|
|
3
3
|
require 'i18n/tasks/scanners/file_scanner'
|
4
4
|
require 'i18n/tasks/scanners/relative_keys'
|
5
5
|
require 'i18n/tasks/scanners/ruby_ast_call_finder'
|
6
|
+
require 'i18n/tasks/scanners/ast_matchers/message_receivers_matcher'
|
6
7
|
require 'parser/current'
|
7
8
|
|
8
|
-
# rubocop:disable Metrics/AbcSize,Metrics/BlockNesting,Metrics/PerceivedComplexity
|
9
|
-
# TODO: make this class more readable.
|
10
|
-
|
11
9
|
module I18n::Tasks::Scanners
|
12
10
|
# Scan for I18n.translate calls using whitequark/parser
|
13
|
-
class RubyAstScanner < FileScanner
|
11
|
+
class RubyAstScanner < FileScanner
|
14
12
|
include RelativeKeys
|
15
13
|
include AST::Sexp
|
16
14
|
|
17
15
|
MAGIC_COMMENT_PREFIX = /\A.\s*i18n-tasks-use\s+/.freeze
|
18
|
-
RECEIVER_MESSAGES = [nil, AST::Node.new(:const, [nil, :I18n])].product(%i[t t! translate translate!])
|
19
16
|
|
20
17
|
def initialize(**args)
|
21
18
|
super(**args)
|
22
19
|
@parser = ::Parser::CurrentRuby.new
|
23
20
|
@magic_comment_parser = ::Parser::CurrentRuby.new
|
24
|
-
@
|
25
|
-
receiver_messages: config[:receiver_messages] || RECEIVER_MESSAGES
|
26
|
-
)
|
21
|
+
@matchers = setup_matchers
|
27
22
|
end
|
28
23
|
|
29
24
|
protected
|
@@ -48,141 +43,10 @@ module I18n::Tasks::Scanners
|
|
48
43
|
@parser.parse_with_comments(make_buffer(path))
|
49
44
|
end
|
50
45
|
|
51
|
-
# @param send_node [Parser::AST::Node]
|
52
|
-
# @param method_name [Symbol, nil]
|
53
|
-
# @param location [Parser::Source::Map]
|
54
|
-
# @return [nil, [key, Occurrence]] full absolute key name and the occurrence.
|
55
|
-
def send_node_to_key_occurrence(send_node, method_name, location: send_node.loc)
|
56
|
-
if (first_arg_node = send_node.children[2]) &&
|
57
|
-
(key = extract_string(first_arg_node))
|
58
|
-
if (second_arg_node = send_node.children[3]) &&
|
59
|
-
second_arg_node.type == :hash
|
60
|
-
if (scope_node = extract_hash_pair(second_arg_node, 'scope'))
|
61
|
-
scope = extract_string(scope_node.children[1],
|
62
|
-
array_join_with: '.', array_flatten: true, array_reject_blank: true)
|
63
|
-
return nil if scope.nil? && scope_node.type != :nil
|
64
|
-
|
65
|
-
key = [scope, key].join('.') unless scope == ''
|
66
|
-
end
|
67
|
-
default_arg = if (default_arg_node = extract_hash_pair(second_arg_node, 'default'))
|
68
|
-
extract_string(default_arg_node.children[1])
|
69
|
-
end
|
70
|
-
end
|
71
|
-
full_key = if send_node.children[0].nil?
|
72
|
-
# Relative keys only work if called via `t()` but not `I18n.t()`:
|
73
|
-
absolute_key(key, location.expression.source_buffer.name, calling_method: method_name)
|
74
|
-
else
|
75
|
-
key
|
76
|
-
end
|
77
|
-
[full_key, range_to_occurrence(key, location.expression, default_arg: default_arg)]
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
# Extract a hash pair with a given literal key.
|
82
|
-
#
|
83
|
-
# @param node [AST::Node] a node of type `:hash`.
|
84
|
-
# @param key [String] node key as a string (indifferent symbol-string matching).
|
85
|
-
# @return [AST::Node, nil] a node of type `:pair` or nil.
|
86
|
-
def extract_hash_pair(node, key)
|
87
|
-
node.children.detect do |child|
|
88
|
-
next unless child.type == :pair
|
89
|
-
|
90
|
-
key_node = child.children[0]
|
91
|
-
%i[sym str].include?(key_node.type) && key_node.children[0].to_s == key
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
# If the node type is of `%i(sym str int false true)`, return the value as a string.
|
96
|
-
# Otherwise, if `config[:strict]` is `false` and the type is of `%i(dstr dsym)`,
|
97
|
-
# return the source as if it were a string.
|
98
|
-
#
|
99
|
-
# @param node [Parser::AST::Node]
|
100
|
-
# @param array_join_with [String, nil] if set to a string, arrays will be processed and their elements joined.
|
101
|
-
# @param array_flatten [Boolean] if true, nested arrays are flattened,
|
102
|
-
# otherwise their source is copied and surrounded by #{}. No effect unless `array_join_with` is set.
|
103
|
-
# @param array_reject_blank [Boolean] if true, empty strings and `nil`s are skipped.
|
104
|
-
# No effect unless `array_join_with` is set.
|
105
|
-
# @return [String, nil] `nil` is returned only when a dynamic value is encountered in strict mode
|
106
|
-
# or the node type is not supported.
|
107
|
-
def extract_string(node, array_join_with: nil, array_flatten: false, array_reject_blank: false)
|
108
|
-
if %i[sym str int].include?(node.type)
|
109
|
-
node.children[0].to_s
|
110
|
-
elsif %i[true false].include?(node.type)
|
111
|
-
node.type.to_s
|
112
|
-
elsif node.type == :nil
|
113
|
-
''
|
114
|
-
elsif node.type == :array && array_join_with
|
115
|
-
extract_array_as_string(
|
116
|
-
node,
|
117
|
-
array_join_with: array_join_with,
|
118
|
-
array_flatten: array_flatten,
|
119
|
-
array_reject_blank: array_reject_blank
|
120
|
-
).tap do |str|
|
121
|
-
# `nil` is returned when a dynamic value is encountered in strict mode. Propagate:
|
122
|
-
return nil if str.nil?
|
123
|
-
end
|
124
|
-
elsif !config[:strict] && %i[dsym dstr].include?(node.type)
|
125
|
-
node.children.map do |child|
|
126
|
-
if %i[sym str].include?(child.type)
|
127
|
-
child.children[0].to_s
|
128
|
-
else
|
129
|
-
child.loc.expression.source
|
130
|
-
end
|
131
|
-
end.join
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
# Extract an array as a single string.
|
136
|
-
#
|
137
|
-
# @param array_join_with [String] joiner of the array elements.
|
138
|
-
# @param array_flatten [Boolean] if true, nested arrays are flattened,
|
139
|
-
# otherwise their source is copied and surrounded by #{}.
|
140
|
-
# @param array_reject_blank [Boolean] if true, empty strings and `nil`s are skipped.
|
141
|
-
# @return [String, nil] `nil` is returned only when a dynamic value is encountered in strict mode.
|
142
|
-
def extract_array_as_string(node, array_join_with:, array_flatten: false, array_reject_blank: false)
|
143
|
-
children_strings = node.children.map do |child|
|
144
|
-
if %i[sym str int true false].include?(child.type)
|
145
|
-
extract_string child
|
146
|
-
else
|
147
|
-
# ignore dynamic argument in strict mode
|
148
|
-
return nil if config[:strict]
|
149
|
-
|
150
|
-
if %i[dsym dstr].include?(child.type) || (child.type == :array && array_flatten)
|
151
|
-
extract_string(child, array_join_with: array_join_with)
|
152
|
-
else
|
153
|
-
"\#{#{child.loc.expression.source}}"
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
if array_reject_blank
|
158
|
-
children_strings.reject! do |x|
|
159
|
-
# empty strings and nils in the scope argument are ignored by i18n
|
160
|
-
x == ''
|
161
|
-
end
|
162
|
-
end
|
163
|
-
children_strings.join(array_join_with)
|
164
|
-
end
|
165
|
-
|
166
46
|
def keys_relative_to_calling_method?(path)
|
167
47
|
/controllers|mailers/.match(path)
|
168
48
|
end
|
169
49
|
|
170
|
-
# @param raw_key [String]
|
171
|
-
# @param range [Parser::Source::Range]
|
172
|
-
# @param default_arg [String, nil]
|
173
|
-
# @return [Results::Occurrence]
|
174
|
-
def range_to_occurrence(raw_key, range, default_arg: nil)
|
175
|
-
Results::Occurrence.new(
|
176
|
-
path: range.source_buffer.name,
|
177
|
-
pos: range.begin_pos,
|
178
|
-
line_num: range.line,
|
179
|
-
line_pos: range.column,
|
180
|
-
line: range.source_line,
|
181
|
-
raw_key: raw_key,
|
182
|
-
default_arg: default_arg
|
183
|
-
)
|
184
|
-
end
|
185
|
-
|
186
50
|
# Create an {Parser::Source::Buffer} with the given contents.
|
187
51
|
# The contents are assigned a {Parser::Source::Buffer#raw_source}.
|
188
52
|
#
|
@@ -211,12 +75,23 @@ module I18n::Tasks::Scanners
|
|
211
75
|
magic_comments.flat_map do |comment|
|
212
76
|
@parser.reset
|
213
77
|
associated_node = comment_to_node[comment]
|
214
|
-
@
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
78
|
+
ast = @parser.parse(make_buffer(path, comment.text.sub(MAGIC_COMMENT_PREFIX, '').split(/\s+(?=t)/).join('; ')))
|
79
|
+
calls = RubyAstCallFinder.new.collect_calls(ast)
|
80
|
+
results = []
|
81
|
+
|
82
|
+
# method_name is not available at this stage
|
83
|
+
calls.each do |send_node, _method_name|
|
84
|
+
@matchers.each do |matcher|
|
85
|
+
result = matcher.convert_to_key_occurrences(
|
86
|
+
send_node,
|
87
|
+
nil,
|
88
|
+
location: associated_node || comment.location
|
89
|
+
)
|
90
|
+
results << result if result
|
91
|
+
end
|
219
92
|
end
|
93
|
+
|
94
|
+
results
|
220
95
|
end
|
221
96
|
end
|
222
97
|
|
@@ -225,10 +100,39 @@ module I18n::Tasks::Scanners
|
|
225
100
|
# @param ast {Parser::Source::Comment}
|
226
101
|
# @return [nil, [key, Occurrence]] full absolute key name and the occurrence.
|
227
102
|
def ast_to_occurences(ast)
|
228
|
-
|
229
|
-
|
103
|
+
calls = RubyAstCallFinder.new.collect_calls(ast)
|
104
|
+
results = []
|
105
|
+
calls.each do |send_node, method_name|
|
106
|
+
@matchers.each do |matcher|
|
107
|
+
result = matcher.convert_to_key_occurrences(send_node, method_name)
|
108
|
+
results << result if result
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
results
|
113
|
+
end
|
114
|
+
|
115
|
+
def setup_matchers
|
116
|
+
if config[:receiver_messages]
|
117
|
+
config[:receiver_messages].map do |receiver, message|
|
118
|
+
AstMatchers::MessageReceiversMatcher.new(
|
119
|
+
receivers: [receiver],
|
120
|
+
message: message,
|
121
|
+
scanner: self
|
122
|
+
)
|
123
|
+
end
|
124
|
+
else
|
125
|
+
%i[t t! translate translate!].map do |message|
|
126
|
+
AstMatchers::MessageReceiversMatcher.new(
|
127
|
+
receivers: [
|
128
|
+
AST::Node.new(:const, [nil, :I18n]),
|
129
|
+
nil
|
130
|
+
],
|
131
|
+
message: message,
|
132
|
+
scanner: self
|
133
|
+
)
|
134
|
+
end
|
230
135
|
end
|
231
136
|
end
|
232
137
|
end
|
233
138
|
end
|
234
|
-
# rubocop:enable Metrics/AbcSize,Metrics/BlockNesting,Metrics/PerceivedComplexity
|
data/lib/i18n/tasks/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: i18n-tasks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- glebm
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-04-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -371,6 +371,8 @@ files:
|
|
371
371
|
- lib/i18n/tasks/references.rb
|
372
372
|
- lib/i18n/tasks/reports/base.rb
|
373
373
|
- lib/i18n/tasks/reports/terminal.rb
|
374
|
+
- lib/i18n/tasks/scanners/ast_matchers/base_matcher.rb
|
375
|
+
- lib/i18n/tasks/scanners/ast_matchers/message_receivers_matcher.rb
|
374
376
|
- lib/i18n/tasks/scanners/erb_ast_processor.rb
|
375
377
|
- lib/i18n/tasks/scanners/erb_ast_scanner.rb
|
376
378
|
- lib/i18n/tasks/scanners/file_scanner.rb
|