Skip to content

Commit

Permalink
Ignore keys in views with non-literal scope arg
Browse files Browse the repository at this point in the history
Fixes #213
  • Loading branch information
glebm committed Jan 19, 2017
1 parent fd904f2 commit 8e6b327
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 12 deletions.
50 changes: 38 additions & 12 deletions lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,14 @@ def match_to_key(match, path, location)
key = super
scope = match[1]
if scope
scope_ns = scope.gsub(/[\[\]\s]+/, '').split(',').map { |arg| strip_literal(arg) } * '.'
"#{scope_ns}.#{key}"
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

# also parse expressions with literals
def literal_re
/(?: (?: #{super} ) | #{expr_re} )/x
end

# strip literals, convert expressions to #{interpolations}
def strip_literal(val)
if val =~ /\A\w/
Expand All @@ -49,7 +45,7 @@ def scope_arg_re
/(?:
:scope\s*=>\s* | (?# :scope => :home )
scope:\s* (?# scope: :home )
) (#{array_or_one_literal_re})/x
) ([^\n]*)/x
end

# match code expression
Expand All @@ -59,10 +55,40 @@ def expr_re
/[\w():"'.\s]+/x
end

# match +el+ or array of +el+
def array_or_one_literal_re(el = literal_re)
/#{el} |
\[\s* (?:#{el}\s*,\s*)* #{el} \s*\]/x
# extract literal or array of literals
# returns nil on any other input
# rubocop:disable Metrics/CyclomaticComplexity,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/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
end
end
1 change: 1 addition & 0 deletions spec/fixtures/app/views/index.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ p = Spree.t 'not_a_key'
= t 'reference-missing-target.a'

p = t 'missing_key_ending_in_colon.key:'
p = t(:ignored_in_strict_mode, scope: [:search, params[:action]]), query: params[:q]
59 changes: 59 additions & 0 deletions spec/pattern_with_scope_scanner_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# frozen_string_literal: true
require 'spec_helper'

RSpec.describe 'PatternWithScopeScanner' do
def stub_source(scanner, source)
allow(scanner).to receive(:traverse_files) { |&block| [block.call('test')] }
allow(scanner).to receive(:read_file) { source }
end

describe 'match_to_key' do
let(:scanner) do
I18n::Tasks::Scanners::PatternWithScopeScanner.new
end

it 'matches a literal scope' do
stub_source scanner, '= t :key, scope: "scope"'
expect(scanner.keys.map(&:key)).to eq(['scope.key'])

stub_source scanner, '= t :key, scope: :scope'
expect(scanner.keys.map(&:key)).to eq(['scope.key'])

stub_source scanner, '= t :key, :scope => :scope'
expect(scanner.keys.map(&:key)).to eq(['scope.key'])

stub_source scanner, '= t :key, :scope => :scope, default: "Default"'
expect(scanner.keys.map(&:key)).to eq(['scope.key'])
end

it 'matches an array of literals scope' do
stub_source scanner, '= t :key, :scope => [:a, :b]'
expect(scanner.keys.map(&:key)).to eq(['a.b.key'])
end

it 'does not match anything else' do
stub_source scanner, '= t :key, scope: a'
expect(scanner.keys.map(&:key)).to eq([])

stub_source scanner, '= t :key, scope: []'
expect(scanner.keys.map(&:key)).to eq([])

stub_source scanner, '= t :key, scope: [a]'
expect(scanner.keys.map(&:key)).to eq([])

stub_source scanner, '= t :key, scope: [:x, [:y]]'
expect(scanner.keys.map(&:key)).to eq([])

stub_source scanner, '= t :key, scope: (a)'
expect(scanner.keys.map(&:key)).to eq([])
end

it 'matches only the scope argument' do
stub_source scanner, '= t :key, :scope => [:a, :b] :c'
expect(scanner.keys.map(&:key)).to eq(['a.b.key'])

stub_source scanner, '= t :key, :scope => [:a, :b], :c'
expect(scanner.keys.map(&:key)).to eq(['a.b.key'])
end
end
end

0 comments on commit 8e6b327

Please sign in to comment.