i18n-tasks 1.0.14 → 1.1.0

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.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +138 -39
  3. data/Rakefile +4 -4
  4. data/bin/i18n-tasks +3 -3
  5. data/config/locales/en.yml +17 -1
  6. data/config/locales/ru.yml +18 -1
  7. data/i18n-tasks.gemspec +28 -38
  8. data/lib/i18n/tasks/base_task.rb +19 -19
  9. data/lib/i18n/tasks/cli.rb +37 -30
  10. data/lib/i18n/tasks/command/collection.rb +4 -4
  11. data/lib/i18n/tasks/command/commander.rb +5 -5
  12. data/lib/i18n/tasks/command/commands/check_prism.rb +126 -0
  13. data/lib/i18n/tasks/command/commands/data.rb +33 -33
  14. data/lib/i18n/tasks/command/commands/eq_base.rb +3 -3
  15. data/lib/i18n/tasks/command/commands/health.rb +6 -5
  16. data/lib/i18n/tasks/command/commands/interpolations.rb +14 -3
  17. data/lib/i18n/tasks/command/commands/meta.rb +6 -6
  18. data/lib/i18n/tasks/command/commands/missing.rb +28 -26
  19. data/lib/i18n/tasks/command/commands/tree.rb +33 -33
  20. data/lib/i18n/tasks/command/commands/usages.rb +24 -24
  21. data/lib/i18n/tasks/command/dsl.rb +1 -1
  22. data/lib/i18n/tasks/command/option_parsers/enum.rb +8 -7
  23. data/lib/i18n/tasks/command/option_parsers/locale.rb +4 -4
  24. data/lib/i18n/tasks/command/options/common.rb +16 -16
  25. data/lib/i18n/tasks/command/options/data.rb +18 -18
  26. data/lib/i18n/tasks/command/options/locales.rb +33 -24
  27. data/lib/i18n/tasks/commands.rb +14 -12
  28. data/lib/i18n/tasks/concurrent/cache.rb +1 -1
  29. data/lib/i18n/tasks/concurrent/cached_value.rb +1 -1
  30. data/lib/i18n/tasks/configuration.rb +26 -20
  31. data/lib/i18n/tasks/console_context.rb +11 -11
  32. data/lib/i18n/tasks/data/adapter/json_adapter.rb +1 -1
  33. data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +5 -5
  34. data/lib/i18n/tasks/data/file_formats.rb +3 -3
  35. data/lib/i18n/tasks/data/file_system.rb +5 -5
  36. data/lib/i18n/tasks/data/file_system_base.rb +26 -26
  37. data/lib/i18n/tasks/data/language_names.rb +202 -0
  38. data/lib/i18n/tasks/data/router/conservative_router.rb +3 -3
  39. data/lib/i18n/tasks/data/router/isolating_router.rb +19 -19
  40. data/lib/i18n/tasks/data/router/pattern_router.rb +5 -5
  41. data/lib/i18n/tasks/data/tree/node.rb +27 -27
  42. data/lib/i18n/tasks/data/tree/nodes.rb +10 -10
  43. data/lib/i18n/tasks/data/tree/siblings.rb +20 -20
  44. data/lib/i18n/tasks/data/tree/traversal.rb +5 -5
  45. data/lib/i18n/tasks/data.rb +4 -4
  46. data/lib/i18n/tasks/html_keys.rb +2 -2
  47. data/lib/i18n/tasks/ignore_keys.rb +9 -9
  48. data/lib/i18n/tasks/interpolations.rb +21 -1
  49. data/lib/i18n/tasks/key_pattern_matching.rb +8 -8
  50. data/lib/i18n/tasks/logging.rb +2 -1
  51. data/lib/i18n/tasks/missing_keys.rb +24 -8
  52. data/lib/i18n/tasks/plural_keys.rb +6 -4
  53. data/lib/i18n/tasks/references.rb +4 -4
  54. data/lib/i18n/tasks/reports/base.rb +18 -14
  55. data/lib/i18n/tasks/reports/terminal.rb +64 -47
  56. data/lib/i18n/tasks/scanners/ast_matchers/base_matcher.rb +3 -3
  57. data/lib/i18n/tasks/scanners/ast_matchers/default_i18n_subject_matcher.rb +3 -3
  58. data/lib/i18n/tasks/scanners/ast_matchers/message_receivers_matcher.rb +10 -10
  59. data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +2 -2
  60. data/lib/i18n/tasks/scanners/erb_ast_scanner.rb +69 -10
  61. data/lib/i18n/tasks/scanners/file_scanner.rb +5 -5
  62. data/lib/i18n/tasks/scanners/files/caching_file_finder.rb +3 -3
  63. data/lib/i18n/tasks/scanners/files/caching_file_finder_provider.rb +3 -3
  64. data/lib/i18n/tasks/scanners/files/caching_file_reader.rb +2 -2
  65. data/lib/i18n/tasks/scanners/files/file_finder.rb +8 -8
  66. data/lib/i18n/tasks/scanners/files/file_reader.rb +1 -1
  67. data/lib/i18n/tasks/scanners/local_ruby_parser.rb +9 -9
  68. data/lib/i18n/tasks/scanners/occurrence_from_position.rb +1 -1
  69. data/lib/i18n/tasks/scanners/pattern_mapper.rb +7 -7
  70. data/lib/i18n/tasks/scanners/pattern_scanner.rb +20 -20
  71. data/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb +8 -8
  72. data/lib/i18n/tasks/scanners/prism_scanners/arguments_visitor.rb +48 -0
  73. data/lib/i18n/tasks/scanners/prism_scanners/nodes.rb +374 -0
  74. data/lib/i18n/tasks/scanners/prism_scanners/visitor.rb +337 -0
  75. data/lib/i18n/tasks/scanners/relative_keys.rb +8 -8
  76. data/lib/i18n/tasks/scanners/results/key_occurrences.rb +3 -3
  77. data/lib/i18n/tasks/scanners/results/occurrence.rb +14 -10
  78. data/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb +1 -1
  79. data/lib/i18n/tasks/scanners/ruby_key_literals.rb +6 -6
  80. data/lib/i18n/tasks/scanners/ruby_parser_factory.rb +27 -0
  81. data/lib/i18n/tasks/scanners/ruby_scanner.rb +225 -0
  82. data/lib/i18n/tasks/scanners/scanner.rb +2 -2
  83. data/lib/i18n/tasks/scanners/scanner_multiplexer.rb +1 -1
  84. data/lib/i18n/tasks/split_key.rb +4 -4
  85. data/lib/i18n/tasks/stats.rb +3 -3
  86. data/lib/i18n/tasks/translation.rb +8 -5
  87. data/lib/i18n/tasks/translators/base_translator.rb +43 -13
  88. data/lib/i18n/tasks/translators/deepl_translator.rb +22 -14
  89. data/lib/i18n/tasks/translators/google_translator.rb +178 -26
  90. data/lib/i18n/tasks/translators/openai_translator.rb +56 -31
  91. data/lib/i18n/tasks/translators/watsonx_translator.rb +155 -0
  92. data/lib/i18n/tasks/translators/yandex_translator.rb +13 -9
  93. data/lib/i18n/tasks/unused_keys.rb +1 -1
  94. data/lib/i18n/tasks/used_keys.rb +32 -32
  95. data/lib/i18n/tasks/version.rb +1 -1
  96. data/lib/i18n/tasks.rb +17 -16
  97. data/templates/config/i18n-tasks.yml +14 -2
  98. data/templates/minitest/i18n_test.rb +3 -3
  99. data/templates/rspec/i18n_spec.rb +7 -7
  100. metadata +38 -172
  101. data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +0 -145
@@ -1,16 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n/tasks/scanners/ruby_ast_scanner'
4
- require 'i18n/tasks/scanners/local_ruby_parser'
3
+ require "i18n/tasks/scanners/ruby_scanner"
4
+ require "i18n/tasks/scanners/local_ruby_parser"
5
+ require "i18n/tasks/scanners/occurrence_from_position"
6
+ require "prism"
5
7
 
6
8
  module I18n::Tasks::Scanners
7
- # Scan for I18n.translate calls in ERB-file better-html and ASTs
8
- class ErbAstScanner < RubyAstScanner
9
- DEFAULT_REGEXP = /<%(={1,2}|-|\#|%)?(.*?)([-=])?%>/m.freeze
9
+ # Scan for I18n.translate calls in ERB-file using regexp and Parser/Prism
10
+ class ErbAstScanner < RubyScanner
11
+ include OccurrenceFromPosition
10
12
 
11
- def initialize(**args)
12
- super(**args)
13
- @ruby_parser = LocalRubyParser.new(ignore_blocks: true)
13
+ DEFAULT_REGEXP = /<%(={1,2}|-|\#-?|%)?(.*?)([-=])?%>/m
14
+
15
+ # Parser scanner, method called in RubyScanner
16
+ def ast_parser_parse_file(path)
17
+ @ruby_parser ||= LocalRubyParser.new(ignore_blocks: true)
18
+ super
14
19
  end
15
20
 
16
21
  private
@@ -34,11 +39,11 @@ module I18n::Tasks::Scanners
34
39
  stop = match.end(0) - 2 - (tailch&.size || 0)
35
40
 
36
41
  case character
37
- when '=', nil, '-'
42
+ when "=", nil, "-"
38
43
  parsed, parsed_comments = handle_code(buffer, code, start, stop)
39
44
  comments.concat(parsed_comments)
40
45
  children << parsed unless parsed.nil?
41
- when '#', '#-'
46
+ when "#", "#-"
42
47
  comments << handle_comment(buffer, start, stop)
43
48
  end
44
49
  end
@@ -74,5 +79,59 @@ module I18n::Tasks::Scanners
74
79
  )
75
80
  ::Parser::AST::Node.new(:erb, children, location: location)
76
81
  end
82
+
83
+ # Prism scanner, method called in RubyScanner
84
+ def prism_parse_file(path)
85
+ occurrences = []
86
+ content = File.read(path)
87
+
88
+ content.scan(DEFAULT_REGEXP) do |indicator, code, _tailch, _rspace|
89
+ match = Regexp.last_match
90
+ character = indicator ? indicator[0] : nil
91
+ start = match.begin(0) + 2 + (character&.size || 0)
92
+
93
+ case character
94
+ when "=", nil, "-"
95
+ occurrences += process_code(path, code, content, start)
96
+ when "#", "#-"
97
+ occurrences += process_comments(path, code, content, start)
98
+ end
99
+ end
100
+
101
+ occurrences
102
+ end
103
+
104
+ def process_code(path, code, content, start)
105
+ return [] if code.strip.empty? # skip empty ERB tags
106
+
107
+ process_prism_results(path, Prism.parse(code)).map do |key, occurrence|
108
+ [
109
+ key,
110
+ occurrence_from_position(
111
+ path,
112
+ content,
113
+ start + occurrence.pos,
114
+ raw_key: occurrence.raw_key
115
+ )
116
+ ]
117
+ end
118
+ end
119
+
120
+ def process_comments(path, code, content, start)
121
+ return [] if code.strip.empty?
122
+
123
+ parsed = Prism.parse(code.gsub("i18n-tasks-use ", "#i18n-tasks-use "))
124
+ process_prism_results(path, parsed).map do |key, occurrence|
125
+ [
126
+ key,
127
+ occurrence_from_position(
128
+ path,
129
+ content,
130
+ start + (code.index(key) || occurrence.pos),
131
+ raw_key: occurrence.raw_key
132
+ )
133
+ ]
134
+ end
135
+ end
77
136
  end
78
137
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n/tasks/scanners/scanner'
3
+ require "i18n/tasks/scanners/scanner"
4
4
 
5
5
  module I18n::Tasks::Scanners
6
6
  # A base class for a scanner that analyses files.
@@ -16,7 +16,7 @@ module I18n::Tasks::Scanners
16
16
  file_reader: Files::CachingFileReader.new
17
17
  )
18
18
  super()
19
- @config = config
19
+ @config = config
20
20
  @file_reader = file_reader
21
21
  @file_finder = file_finder_provider.get(**config.slice(:paths, :only, :exclude))
22
22
  end
@@ -36,7 +36,7 @@ module I18n::Tasks::Scanners
36
36
  #
37
37
  # @return [Array<[key, Results::KeyOccurrence]>] each occurrence found in the file
38
38
  def scan_file(_path)
39
- fail 'Unimplemented'
39
+ fail "Unimplemented"
40
40
  end
41
41
 
42
42
  # Read a file. Reads of the same path are cached.
@@ -53,8 +53,8 @@ module I18n::Tasks::Scanners
53
53
  # @param (see FileFinder#traverse_files)
54
54
  # @yieldparam (see FileFinder#traverse_files)
55
55
  # @return (see FileFinder#traverse_files)
56
- def traverse_files(&block)
57
- @file_finder.traverse_files(&block)
56
+ def traverse_files(&)
57
+ @file_finder.traverse_files(&)
58
58
  end
59
59
 
60
60
  # @note This method is cached, it will only access the filesystem on the first invocation.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n/tasks/concurrent/cached_value'
4
- require 'i18n/tasks/scanners/files/file_finder'
3
+ require "i18n/tasks/concurrent/cached_value"
4
+ require "i18n/tasks/scanners/files/file_finder"
5
5
 
6
6
  module I18n::Tasks::Scanners::Files
7
7
  # Finds the files in the specified search paths with support for exclusion / inclusion patterns.
@@ -23,7 +23,7 @@ module I18n::Tasks::Scanners::Files
23
23
  # @yieldparam (see FileFinder#traverse_files)
24
24
  # @return (see FileFinder#traverse_files)
25
25
 
26
- alias uncached_find_files find_files
26
+ alias_method :uncached_find_files, :find_files
27
27
  private :uncached_find_files
28
28
 
29
29
  # @note This method is cached, it will only access the filesystem on the first invocation.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n/tasks/concurrent/cache'
4
- require 'i18n/tasks/scanners/files/caching_file_finder'
3
+ require "i18n/tasks/concurrent/cache"
4
+ require "i18n/tasks/scanners/files/caching_file_finder"
5
5
 
6
6
  module I18n::Tasks::Scanners::Files
7
7
  # Finds the files and provides their contents.
@@ -12,7 +12,7 @@ module I18n::Tasks::Scanners::Files
12
12
  # @param exclude [Array<String>]
13
13
  def initialize(exclude: [])
14
14
  @cache = ::I18n::Tasks::Concurrent::Cache.new
15
- @defaults = { exclude: exclude }
15
+ @defaults = {exclude: exclude}
16
16
  end
17
17
 
18
18
  # Initialize a {CachingFileFinder} or get one from cache based on the constructor arguments.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n/tasks/concurrent/cache'
4
- require 'i18n/tasks/scanners/files/file_reader'
3
+ require "i18n/tasks/concurrent/cache"
4
+ require "i18n/tasks/scanners/files/file_reader"
5
5
 
6
6
  module I18n::Tasks::Scanners::Files
7
7
  # Reads the files in 'rb' mode and UTF-8 encoding.
@@ -13,10 +13,10 @@ module I18n::Tasks::Scanners::Files
13
13
  # Files not matching any of the inclusion patterns will be excluded.
14
14
  # @param exclude [Arry<String>] {File.fnmatch}-compatible patterns of files to exclude.
15
15
  # Files matching any of the exclusion patterns will be excluded even if they match an inclusion pattern.
16
- def initialize(paths: ['.'], only: nil, exclude: [])
17
- fail 'paths argument is required' if paths.nil?
16
+ def initialize(paths: ["."], only: nil, exclude: [])
17
+ fail "paths argument is required" if paths.nil?
18
18
 
19
- @paths = paths
19
+ @paths = paths
20
20
  @include = only
21
21
  @exclude = exclude || []
22
22
  end
@@ -26,8 +26,8 @@ module I18n::Tasks::Scanners::Files
26
26
  # @yield [path]
27
27
  # @yieldparam path [String] the path of the found file.
28
28
  # @return [Array<of block results>]
29
- def traverse_files(&block)
30
- find_files.map(&block)
29
+ def traverse_files(&)
30
+ find_files.map(&)
31
31
  end
32
32
 
33
33
  # @return [Array<String>] found files
@@ -36,10 +36,10 @@ module I18n::Tasks::Scanners::Files
36
36
  paths = @paths.select { |p| File.exist?(p) }
37
37
  log_warn "None of the search.paths exist #{@paths.inspect}" if paths.empty?
38
38
  Find.find(*paths) do |path|
39
- is_dir = File.directory?(path)
40
- hidden = File.basename(path).start_with?('.') && !%w[. ./].include?(path)
39
+ is_dir = File.directory?(path)
40
+ hidden = File.basename(path).start_with?(".") && !%w[. ./].include?(path)
41
41
  not_incl = @include && !path_fnmatch_any?(path, @include)
42
- excl = path_fnmatch_any?(path, @exclude)
42
+ excl = path_fnmatch_any?(path, @exclude)
43
43
  if is_dir || hidden || not_incl || excl
44
44
  Find.prune if is_dir && (hidden || excl)
45
45
  else
@@ -12,7 +12,7 @@ module I18n::Tasks::Scanners::Files
12
12
  # @return [String] file contents
13
13
  def read_file(path)
14
14
  result = nil
15
- File.open(path, 'rb', encoding: 'UTF-8') { |f| result = f.read }
15
+ File.open(path, "rb", encoding: "UTF-8") { |f| result = f.read }
16
16
  result
17
17
  end
18
18
  end
@@ -1,26 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'parser/current'
3
+ require "i18n/tasks/scanners/ruby_parser_factory"
4
4
 
5
5
  module I18n::Tasks::Scanners
6
6
  class LocalRubyParser
7
7
  # ignore_blocks feature inspired by shopify/better-html
8
8
  # https://github.com/Shopify/better-html/blob/087943ffd2a5877fa977d71532010b0c91239519/lib/better_html/test_helper/ruby_node.rb#L24
9
- BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/.freeze
9
+ BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
10
10
 
11
11
  def initialize(ignore_blocks: false)
12
- @parser = ::Parser::CurrentRuby.new
12
+ @parser = RubyParserFactory.create_parser
13
13
  @ignore_blocks = ignore_blocks
14
14
  end
15
15
 
16
16
  # Parse string and normalize location
17
17
  def parse(source, location: nil)
18
- buffer = ::Parser::Source::Buffer.new('(string)')
18
+ buffer = ::Parser::Source::Buffer.new("(string)")
19
19
  buffer.source = if @ignore_blocks
20
- source.sub(BLOCK_EXPR, '')
21
- else
22
- source
23
- end
20
+ source.sub(BLOCK_EXPR, "")
21
+ else
22
+ source
23
+ end
24
24
 
25
25
  @parser.reset
26
26
  ast, comments = @parser.parse_with_comments(buffer)
@@ -42,7 +42,7 @@ module I18n::Tasks::Scanners
42
42
  node.updated(
43
43
  nil,
44
44
  node.children.map { |child| normalize_location(child, location) },
45
- { location: updated_location(location, node.location) }
45
+ {location: updated_location(location, node.location)}
46
46
  )
47
47
  end
48
48
 
@@ -13,7 +13,7 @@ module I18n
13
13
  # @return [Results::Occurrence]
14
14
  def occurrence_from_position(path, contents, position, raw_key: nil)
15
15
  line_begin = contents.rindex(/^/, position - 1)
16
- line_end = contents.index(/.(?=\r?\n|$)/, position)
16
+ line_end = contents.index(/.(?=\r?\n|$)/, position)
17
17
  Results::Occurrence.new(
18
18
  path: path,
19
19
  pos: position,
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n/tasks/scanners/file_scanner'
4
- require 'i18n/tasks/scanners/relative_keys'
5
- require 'i18n/tasks/scanners/occurrence_from_position'
6
- require 'i18n/tasks/scanners/ruby_key_literals'
3
+ require "i18n/tasks/scanners/file_scanner"
4
+ require "i18n/tasks/scanners/relative_keys"
5
+ require "i18n/tasks/scanners/occurrence_from_position"
6
+ require "i18n/tasks/scanners/ruby_key_literals"
7
7
 
8
8
  module I18n::Tasks::Scanners
9
9
  # Maps the provided patterns to keys.
@@ -34,14 +34,14 @@ module I18n::Tasks::Scanners
34
34
  @patterns.flat_map do |pattern, key|
35
35
  result = []
36
36
  text.scan(pattern) do |_|
37
- match = Regexp.last_match
38
- matches = match.names.map(&:to_sym).zip(match.captures).to_h
37
+ match = Regexp.last_match
38
+ matches = match.names.map(&:to_sym).zip(match.captures).to_h
39
39
  if matches.key?(:key)
40
40
  matches[:key] = strip_literal(matches[:key])
41
41
  next unless valid_key?(matches[:key])
42
42
  end
43
43
  result << [absolute_key(format(key, matches), path),
44
- occurrence_from_position(path, text, match.offset(0).first)]
44
+ occurrence_from_position(path, text, match.offset(0).first)]
45
45
  end
46
46
  result
47
47
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n/tasks/scanners/file_scanner'
4
- require 'i18n/tasks/scanners/relative_keys'
5
- require 'i18n/tasks/scanners/occurrence_from_position'
6
- require 'i18n/tasks/scanners/ruby_key_literals'
3
+ require "i18n/tasks/scanners/file_scanner"
4
+ require "i18n/tasks/scanners/relative_keys"
5
+ require "i18n/tasks/scanners/occurrence_from_position"
6
+ require "i18n/tasks/scanners/ruby_key_literals"
7
7
 
8
8
  module I18n::Tasks::Scanners
9
9
  # Scan for I18n.t usages using a simple regular expression.
@@ -12,15 +12,15 @@ module I18n::Tasks::Scanners
12
12
  include OccurrenceFromPosition
13
13
  include RubyKeyLiterals
14
14
 
15
- TRANSLATE_CALL_RE = /(?<=^|[^\p{L}'\-.]|[^\p{L}'-]I18n\.|I18n\.)t(?:!|ranslate!?)?/.freeze
15
+ TRANSLATE_CALL_RE = /(?<=^|[^\p{L}_'\-.]|[^\p{L}'-]I18n\.|I18n\.)t(?:!|ranslate!?)?/
16
16
  IGNORE_LINES = {
17
- 'coffee' => /^\s*#(?!\si18n-tasks-use)/,
18
- 'erb' => /^\s*<%\s*#(?!\si18n-tasks-use)/,
19
- 'es6' => %r{^\s*//(?!\si18n-tasks-use)},
20
- 'haml' => /^\s*-\s*#(?!\si18n-tasks-use)/,
21
- 'js' => %r{^\s*//(?!\si18n-tasks-use)},
22
- 'opal' => /^\s*#(?!\si18n-tasks-use)/,
23
- 'slim' => %r{^\s*(?:-#|/)(?!\si18n-tasks-use)}
17
+ "coffee" => /^\s*#(?!\si18n-tasks-use)/,
18
+ "erb" => /^\s*<%\s*#(?!\si18n-tasks-use)/,
19
+ "es6" => %r{^\s*//(?!\si18n-tasks-use)},
20
+ "haml" => /^\s*-\s*#(?!\si18n-tasks-use)/,
21
+ "js" => %r{^\s*//(?!\si18n-tasks-use)},
22
+ "opal" => /^\s*#(?!\si18n-tasks-use)/,
23
+ "slim" => %r{^\s*(?:-#|/)(?!\si18n-tasks-use)}
24
24
  }.freeze
25
25
 
26
26
  def initialize(**args)
@@ -47,13 +47,13 @@ module I18n::Tasks::Scanners
47
47
  key = match_to_key(match, path, location)
48
48
  next unless key
49
49
 
50
- key += ':' if key.end_with?('.')
50
+ key += ":" if key.end_with?(".")
51
51
  next unless valid_key?(key)
52
52
 
53
53
  keys << [key, location]
54
54
  end
55
55
  keys
56
- rescue Exception => e # rubocop:disable Lint/RescueException
56
+ rescue => e
57
57
  raise ::I18n::Tasks::CommandError.new(e, "Error scanning #{path}: #{e.message}")
58
58
  end
59
59
 
@@ -62,7 +62,7 @@ module I18n::Tasks::Scanners
62
62
  # @return [String] full absolute key name
63
63
  def match_to_key(match, path, location)
64
64
  absolute_key(strip_literal(match[0]), path,
65
- calling_method: -> { closest_method(location) if key_relative_to_method?(path) })
65
+ calling_method: -> { closest_method(location) if key_relative_to_method?(path) })
66
66
  end
67
67
 
68
68
  def exclude_line?(line, path)
@@ -70,11 +70,11 @@ module I18n::Tasks::Scanners
70
70
  re && re =~ line
71
71
  end
72
72
 
73
- VALID_KEY_RE_DYNAMIC = /^(#{VALID_KEY_CHARS}|[:\#{@}\[\]])+$/.freeze
73
+ VALID_KEY_RE_DYNAMIC = /^(#{VALID_KEY_CHARS}|[:\#{@}\[\]])+$/
74
74
 
75
75
  def valid_key?(key)
76
76
  if @config[:strict]
77
- super(key)
77
+ super
78
78
  else
79
79
  key =~ VALID_KEY_RE_DYNAMIC
80
80
  end
@@ -85,9 +85,9 @@ module I18n::Tasks::Scanners
85
85
  end
86
86
 
87
87
  def closest_method(occurrence)
88
- method = File.readlines(occurrence.path, encoding: 'UTF-8')
89
- .first(occurrence.line_num - 1).reverse_each.find { |x| x =~ /\bdef\b/ }
90
- method && method.strip.sub(/^def\s*/, '').sub(/[(\s;].*$/, '')
88
+ method = File.readlines(occurrence.path, encoding: "UTF-8")
89
+ .first(occurrence.line_num - 1).reverse_each.find { |x| x =~ /\bdef\b/ }
90
+ method && method.strip.sub(/^def\s*/, "").sub(/[(\s;].*$/, "")
91
91
  end
92
92
 
93
93
  # This method only exists for backwards compatibility with monkey-patches and plugins
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n/tasks/scanners/pattern_scanner'
3
+ require "i18n/tasks/scanners/pattern_scanner"
4
4
 
5
5
  module I18n::Tasks::Scanners
6
6
  # Scans for I18n.t(key, scope: ...) usages
@@ -21,13 +21,13 @@ module I18n::Tasks::Scanners
21
21
  # @param [String] path
22
22
  # @return [String] full absolute key name with scope resolved if any
23
23
  def match_to_key(match, path, location)
24
- key = super
24
+ key = super
25
25
  scope = match[1]
26
26
  if scope
27
27
  scope_parts = extract_literal_or_array_of_literals(scope)
28
28
  return nil if scope_parts.nil? || scope_parts.empty?
29
29
 
30
- "#{scope_parts.join('.')}.#{key}"
30
+ "#{scope_parts.join(".")}.#{key}"
31
31
  else
32
32
  key unless match[0] =~ /\A\w/
33
33
  end
@@ -43,7 +43,7 @@ module I18n::Tasks::Scanners
43
43
  if val =~ /\A[\w@]/
44
44
  "\#{#{val}}"
45
45
  else
46
- super(val)
46
+ super
47
47
  end
48
48
  end
49
49
 
@@ -77,18 +77,18 @@ module I18n::Tasks::Scanners
77
77
  end
78
78
  end
79
79
  s.each_char.with_index do |c, i|
80
- if c == '['
80
+ if c == "["
81
81
  return nil unless braces_stack.empty?
82
82
 
83
83
  braces_stack.push(i)
84
- elsif c == ']'
84
+ elsif c == "]"
85
85
  break
86
- elsif c == ','
86
+ elsif c == ","
87
87
  consume_literal.call
88
88
  break if braces_stack.empty?
89
89
  elsif c =~ VALID_KEY_CHARS || /['":]/ =~ c
90
90
  acc << c
91
- elsif c != ' '
91
+ elsif c != " "
92
92
  return nil
93
93
  end
94
94
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism/visitor"
4
+
5
+ # This class is used to parse the arguments to e.g. a Prism::CallNode and return the values we need
6
+ # for turning them into translations and occurrences.
7
+ # Used in the PrismScanners::Visitor class.
8
+ module I18n::Tasks::Scanners::PrismScanners
9
+ class ArgumentsVisitor < Prism::Visitor
10
+ def visit_keyword_hash_node(node)
11
+ node.child_nodes.each_with_object({}) do |child, hash|
12
+ next if child.type == :assoc_splat_node
13
+
14
+ hash[visit(child.key)] = visit(child.value)
15
+ hash
16
+ end
17
+ end
18
+
19
+ # Cannot handle arguments that are calls
20
+ def visit_call_node(_node)
21
+ nil
22
+ end
23
+
24
+ def visit_symbol_node(node)
25
+ node.value
26
+ end
27
+
28
+ def visit_string_node(node)
29
+ node.content
30
+ end
31
+
32
+ def visit_array_node(node)
33
+ node.child_nodes.map { |child| visit(child) }
34
+ end
35
+
36
+ def visit_arguments_node(node)
37
+ node.child_nodes.map { |child| visit(child) }
38
+ end
39
+
40
+ def visit_integer_node(node)
41
+ node.value
42
+ end
43
+
44
+ def visit_lambda_node(node)
45
+ node
46
+ end
47
+ end
48
+ end