-
Notifications
You must be signed in to change notification settings - Fork 264
/
pattern_with_scope_scanner.rb
100 lines (89 loc) · 2.69 KB
/
pattern_with_scope_scanner.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# frozen_string_literal: true
require 'i18n/tasks/scanners/pattern_scanner'
module I18n::Tasks::Scanners
# Scans for I18n.t(key, scope: ...) usages
# both scope: "literal", and scope: [:array, :of, 'literals'] forms are supported
# Caveat: scope is only detected when it is the first argument
class PatternWithScopeScanner < PatternScanner
protected
def default_pattern
# capture the first argument and scope argument if present
/#{super}
(?: \s*,\s* #{scope_arg_re} )? (?# capture scope in second argument )
/x
end
# Given
# @param [MatchData] match
# @param [String] path
# @return [String] full absolute key name with scope resolved if any
def match_to_key(match, path, location)
key = super
scope = match[1]
if scope
scope_parts = extract_literal_or_array_of_literals(scope)
return nil if scope_parts.nil? || scope_parts.empty?
"#{scope_parts.join('.')}.#{key}"
else
key unless match[0] =~ /\A\w/
end
end
# parse expressions with literals and variable
def first_argument_re
/(?: (?: #{literal_re} ) | #{expr_re} )/x
end
# strip literals, convert expressions to #{interpolations}
def strip_literal(val)
if val =~ /\A[\w@]/
"\#{#{val}}"
else
super(val)
end
end
# scope: literal or code expression or an array of these
def scope_arg_re
/(?:
:scope\s*=>\s* | (?# :scope => :home )
scope:\s* (?# scope: :home )
) (\[[^\n)%#]*\]|[^\n)%#,]*)/x
end
# match a limited subset of code expressions (no parenthesis, commas, etc)
def expr_re
/[\w@.&|\s?!]+/
end
# extract literal or array of literals
# returns nil on any other input
# rubocop:disable Metrics/MethodLength,Metrics/PerceivedComplexity
def extract_literal_or_array_of_literals(s)
literals = []
braces_stack = []
acc = []
consume_literal = proc do
acc_str = acc.join
if acc_str =~ literal_re
literals << strip_literal(acc_str)
acc = []
else
return nil
end
end
s.each_char.with_index do |c, i|
if c == '['
return nil unless braces_stack.empty?
braces_stack.push(i)
elsif c == ']'
break
elsif c == ','
consume_literal.call
break if braces_stack.empty?
elsif c =~ VALID_KEY_CHARS || /['":]/ =~ c
acc << c
elsif c != ' '
return nil
end
end
consume_literal.call unless acc.empty?
literals
end
# rubocop:enable Metrics/MethodLength,Metrics/PerceivedComplexity
end
end