-
Notifications
You must be signed in to change notification settings - Fork 264
/
pattern_scanner.rb
87 lines (75 loc) · 2.79 KB
/
pattern_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
# frozen_string_literal: true
require 'i18n/tasks/scanners/file_scanner'
require 'i18n/tasks/scanners/relative_keys'
require 'i18n/tasks/scanners/occurrence_from_position'
require 'i18n/tasks/scanners/ruby_key_literals'
module I18n::Tasks::Scanners
# Scan for I18n.t usages using a simple regular expression.
class PatternScanner < FileScanner
include RelativeKeys
include OccurrenceFromPosition
include RubyKeyLiterals
def initialize(**args)
super
@pattern = config[:pattern].present? ? Regexp.new(config[:pattern]) : default_pattern
@ignore_lines_res = (config[:ignore_lines] || []).inject({}) { |h, (ext, re)| h.update(ext => Regexp.new(re)) }
end
protected
# Extract i18n keys from file based on the pattern which must capture the key literal.
# @return [Array<[key, Results::Occurrence]>] each occurrence found in the file
def scan_file(path)
keys = []
text = read_file(path)
text.scan(@pattern) do |match|
src_pos = Regexp.last_match.offset(0).first
location = occurrence_from_position(path, text, src_pos, raw_key: strip_literal(match[0]))
next if exclude_line?(location.line, path)
key = match_to_key(match, path, location)
next unless key
key = key + ':'.freeze if key.end_with?('.'.freeze)
next unless valid_key?(key)
keys << [key, location]
end
keys
rescue Exception => e
raise ::I18n::Tasks::CommandError.new(e, "Error scanning #{path}: #{e.message}")
end
# @param [MatchData] match
# @param [String] path
# @return [String] full absolute key name
def match_to_key(match, path, location)
absolute_key(strip_literal(match[0]), path,
calling_method: -> { closest_method(location) if key_relative_to_method?(path) })
end
def exclude_line?(line, path)
re = @ignore_lines_res[File.extname(path)[1..-1]]
re && re =~ line
end
VALID_KEY_RE_DYNAMIC = /^(#{VALID_KEY_CHARS}|[:\#{@}\[\]])+$/
def valid_key?(key)
if @config[:strict]
super(key)
else
key =~ VALID_KEY_RE_DYNAMIC
end
end
def key_relative_to_method?(path)
/controllers|mailers/ =~ path
end
def closest_method(occurrence)
method = File.readlines(occurrence.path, encoding: 'UTF-8'.freeze).
first(occurrence.line_num - 1).reverse_each.find { |x| x =~ /\bdef\b/ }
method && method.strip.sub(/^def\s*/, '').sub(/[\(\s;].*$/, '')
end
def translate_call_re
/(?<=^|[^\w'\-.]|[^\w'\-]I18n\.|I18n\.)t(?:ranslate)?/
end
def default_pattern
# capture only the first argument
/
#{translate_call_re} [\( ] \s* (?# fn call begin )
(#{literal_re}) (?# capture the first argument)
/x
end
end
end