rubocop-i18n 0.0.1 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8d3c4fbbfe759ead1c4d19be86d280092252ca70
4
- data.tar.gz: e516b0a7acbbe8d554cf0fffea3faa243852eb70
3
+ metadata.gz: e2792a2344ae871fa62e564786c03d892bd5072d
4
+ data.tar.gz: 1354b5ac2e3a9606deb1b9c594edf8a23327db38
5
5
  SHA512:
6
- metadata.gz: 7d1ab20e74da22d5ff3f9e57347f460615a2393cda816008a18216dec4f184ce7c7e53b787aaee34a5f60b3352fef868c990cd6a11b9ceafae5c4e5635eba02e
7
- data.tar.gz: 01c62345a6f67c485d48d0a7e524b6073fc6fe93315303d48ed0f6f04f259377b0103790b8098b0465ac340816ea283520203824e93ec228f76cf10e65b23b08
6
+ metadata.gz: 76633906bc7a3ad0612fff96eab7a80eb21bf26f58b95d6a31c8e24ca2958df6c95f079000c5002785ad184711e207b22b2059383d1791665728a3a73330697e
7
+ data.tar.gz: 4f58a7770df70e74ea4d5150140ee35aaef1b31aacd65b776de7c6ceb6f638715756d71bf43dd5a4b870c724496e01fda0e3f95bdee02da3f7755e98935997bc
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ matrix:
3
+ fast_finish: true
4
+ include:
5
+ - rvm: 2.4.1
6
+ notifications:
7
+ email: false
data/README.md CHANGED
@@ -31,12 +31,142 @@ GetText/DecorateFunctionMessage:
31
31
  Enabled: true
32
32
  ```
33
33
 
34
+ ## Cops
35
+
36
+ ### GetText/DecorateFunctionMessage
37
+
38
+ This cop looks for any raise or fail functions and checks that the user visible message is using gettext decoration with the _() function.
39
+ This cop makes sure the message is decorated, as well as checking that the formatting of the message is compliant according to the follow rules.
40
+ This cop supports autocorrecting of [Simple decoration of a message](#Simple-decoration-of-a-message). See the rubocop documentation on how to run autocorrect.
41
+
42
+ #### Simple decoration of a message
43
+
44
+ Simple message strings should be decorated with the _() function
45
+
46
+ ##### Error message thrown
47
+
48
+ ```
49
+ 'raise' function, message string should be decorated
50
+ ```
51
+
52
+ ##### Bad
53
+
54
+ ``` ruby
55
+ raise("Warning")
56
+ ```
57
+
58
+ ##### Good
59
+
60
+ ``` ruby
61
+ raise(_("Warning"))
62
+ ```
63
+
64
+ #### Multi-line message
65
+
66
+ The message should not span multiple lines, it causes issues during the translation process.
67
+
68
+ ##### Error message thrown
69
+
70
+ ```
71
+ 'raise' function, message should not be a multi-line string
72
+ ```
73
+
74
+ ##### Bad
75
+
76
+ ``` ruby
77
+ raise("this is a multi" \
78
+ "line message")
79
+ ```
80
+
81
+ ##### Good
82
+
83
+ ``` ruby
84
+ raise(_("this is a multi line message"))
85
+ ```
86
+
87
+ #### Concatenated message
88
+
89
+ The message should not concatenate multiple strings, it causes issues during translation and with the gettext.
90
+
91
+ ##### Error message thrown
92
+
93
+ ```
94
+ 'raise' function, message should not be a concatenated string
95
+ ```
96
+
97
+ ##### Bad
98
+
99
+ ``` ruby
100
+ raise("this is a concatenated" + "message")
101
+ ```
102
+
103
+ ##### Good
104
+
105
+ ``` ruby
106
+ raise(_("this is a concatenated message"))
107
+ ```
108
+
109
+ #### Interpolated message
110
+
111
+ The message should be formated in this particular style. Otherwise it causes issues during translation and with the gettext gem.
112
+
113
+ ##### Error message thrown
114
+
115
+ ```
116
+ 'raise' function, message should use correctly formatted interpolation
117
+ ```
118
+
119
+ ##### Bad
120
+
121
+ ``` ruby
122
+ raise("this is an interpolated message IE #{variable}")
123
+ ```
124
+
125
+ ##### Good
126
+
127
+ ``` ruby
128
+ raise(_("this is an interpolated message IE %{value0}") % {value0: var,})
129
+ ```
130
+
131
+ #### No decoration and no string detected
132
+
133
+ The raise or fail function does not contain any decoration, or a simple string
134
+
135
+ ##### Error message thrown
136
+
137
+ ```
138
+ 'raise' function, message should be decorated
139
+ ```
140
+
141
+ ##### Bad
142
+
143
+ ``` ruby
144
+ raise(someOtherFuntioncall(foo, "bar"))
145
+ ```
146
+
147
+ ##### Good
148
+
149
+ In this raise or fail function, the message does not contain any decoration at all and the message is not a simple string. It may make sense to convert the message to a simple string. eg [Simple decoration of a message](#Simple-decoration-of-a-message).
150
+ Or ignore this raise or fail function following this [How to ignore rules in code](#How-to-ignore-rules-in-code) section.
151
+
152
+ ## How to ignore rules in code
153
+
154
+ It may be necessary to ignore a cop for a particular piece of code. We follow standard rubocop idioms.
155
+ ``` ruby
156
+ raise("We don't want this translated") # rubocop:disable GetText/DecorateFunctionMessage
157
+ ```
158
+
159
+ ## Known Issues
160
+
161
+ Rubocop currently does not detect Heredoc style messages in functions correctly, which in turn prevents this plugin from detecting them correctly.
162
+
34
163
  ## Development
35
164
 
36
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
165
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that allows you to experiment.
37
166
 
38
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
167
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which creates a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
39
168
 
40
169
  ## Contributing
41
170
 
42
171
  Bug reports and pull requests are welcome on GitHub at https://github.com/highb/rubocop-i18n. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
172
+
@@ -3,92 +3,152 @@ module RuboCop
3
3
  module I18n
4
4
  module GetText
5
5
  class DecorateFunctionMessage < Cop
6
+ SUPPORTED_METHODS = ['raise', 'fail']
7
+ SUPPORTED_DECORATORS = ['_', 'n_', 'N_']
6
8
 
7
9
  def on_send(node)
8
10
  method_name = node.loc.selector.source
9
- return if !/raise|fail/.match(method_name)
10
- if method_name == "raise"
11
- receiver_node, method_name, *arg_nodes = *node
12
- if !arg_nodes.empty? && arg_nodes[0].type == :const && arg_nodes[1]
13
- how_bad_is_it(node, method_name, arg_nodes[1])
14
- end
15
- elsif method_name == "fail"
16
- receiver_node, method_name, *arg_nodes = *node
17
- if !arg_nodes.empty?
18
- how_bad_is_it(node, method_name, arg_nodes[0])
11
+ return if !supported_method_name?(method_name)
12
+ _, method_name, *arg_nodes = *node
13
+ if !arg_nodes.empty? && !already_decorated?(node) && (contains_string?(arg_nodes) || string_constant?(arg_nodes))
14
+ if string_constant?(arg_nodes)
15
+ message_section = arg_nodes[1]
16
+ else
17
+ message_section = arg_nodes[0]
19
18
  end
19
+
20
+ detect_and_report(node, message_section, method_name)
20
21
  end
21
22
  end
22
23
 
23
24
  private
24
25
 
25
- def how_bad_is_it(node, method_name, message)
26
- if message.str_type?
27
- add_offense(message, :expression, "'#{method_name}' should have a decorator around the message")
28
- elsif multiline_offense?(message)
29
- add_offense(message, :expression, "'#{method_name}' should not use a multi-line string")
30
- elsif concatination_offense?(message)
31
- add_offense(message, :expression, "'#{method_name}' should not use a concatenated string")
32
- elsif interpolation_offense?(message)
33
- add_offense(message, :expression, "'#{method_name}' interpolation is a sin")
26
+ def supported_method_name?(method_name)
27
+ SUPPORTED_METHODS.include?(method_name)
28
+ end
29
+
30
+ def already_decorated?(node, parent = nil)
31
+ parent ||= node
32
+
33
+ if node.respond_to?(:loc) && node.loc.respond_to?(:selector)
34
+ return true if SUPPORTED_DECORATORS.include?(node.loc.selector.source)
34
35
  end
36
+
37
+ return false unless node.respond_to?(:children)
38
+
39
+ node.children.any? { |child| already_decorated?(child, parent) }
35
40
  end
36
41
 
37
- def multiline_offense?(message)
38
- found_multiline = false
39
- found_strings = false
40
- message.children.each { |child|
41
- if child == :/
42
- found_multiline = true
43
- elsif ( (!child.nil? && child.class != Symbol) && ( child.str_type? || child.dstr_type? ) )
44
- found_strings = true
45
- end
46
- }
47
- found_multiline && found_strings
42
+ def string_constant?(nodes)
43
+ nodes[0].type == :const && nodes[1]
48
44
  end
49
45
 
50
- def concatination_offense?(message)
51
- found_concat = false
52
- found_strings = false
53
- message.children.each { |child|
54
- if child == :+
55
- found_concat = true
56
- elsif ( (!child.nil? && child.class != Symbol) && ( child.str_type? || child.dstr_type? ) )
57
- found_strings = true
58
- end
59
- }
60
- found_concat && found_strings
46
+ def contains_string?(nodes)
47
+ nodes[0].inspect.include?(":str") || nodes[0].inspect.include?(":dstr")
61
48
  end
62
49
 
63
- def interpolation_offense?(message)
64
- found_funct = false
65
- message.children.each { |child|
66
- if !child.nil? && child.class != Symbol
67
- if child.begin_type? || child.send_type?
68
- found_funct = true
69
- elsif child.dstr_type?
70
- found_funct = true if child.inspect.include?(":send") || child.inspect.include?(":begin")
71
- end
72
- end
73
- }
74
- found_funct
50
+ def detect_and_report(node, message_section, method_name)
51
+ errors = how_bad_is_it(message_section)
52
+ return if errors.empty?
53
+ error_message = "'#{method_name}' function, "
54
+ errors.each do |error|
55
+ error_message << 'message string should be decorated. ' if error == :simple
56
+ error_message << 'message should not be a concatenated string. ' if error == :concatenation
57
+ error_message << 'message should not be a multi-line string. ' if error == :multiline
58
+ error_message << 'message should use correctly formatted interpolation. ' if error == :interpolation
59
+ error_message << 'message should be decorated. ' if error == :no_decoration
60
+ end
61
+ add_offense(message_section, :expression, error_message)
62
+ end
63
+
64
+ def how_bad_is_it(message_section)
65
+ errors = []
66
+
67
+ errors.push :simple if message_section.str_type?
68
+ errors.push :multiline if message_section.multiline?
69
+ errors.push :concatenation if concatenation_offense?(message_section)
70
+ errors.push :interpolation if interpolation_offense?(message_section)
71
+ errors.push :no_decoration if !already_decorated?(message_section)
72
+
73
+ # only display no_decoration, if that is the only problem.
74
+ if errors.size > 1 && errors.include?(:no_decoration)
75
+ errors.delete(:no_decoration)
76
+ end
77
+ errors
78
+ end
79
+
80
+ def concatenation_offense?(node, parent = nil)
81
+ parent ||= node
82
+
83
+ if node.respond_to?(:loc) && node.loc.respond_to?(:selector)
84
+ return true if node.loc.selector.source == '+'
85
+ end
86
+
87
+ return false unless node.respond_to?(:children)
88
+
89
+ node.children.any? { |child| concatenation_offense?(child, parent) }
90
+ end
91
+
92
+ def interpolation_offense?(node, parent = nil)
93
+ parent ||= node
94
+
95
+ return true if node.class == RuboCop::AST::Node && node.dstr_type?
96
+
97
+ return false unless node.respond_to?(:children)
98
+
99
+ node.children.any? { |child| interpolation_offense?(child, parent) }
75
100
  end
76
101
 
77
102
  def autocorrect(node)
78
103
  if node.str_type?
79
104
  single_string_correct(node)
80
- else
81
- multiline_string_correct(node)
105
+ elsif interpolation_offense?(node)
106
+ # interpolation_correct(node)
82
107
  end
83
108
  end
84
109
 
85
110
  def single_string_correct(node)
86
- ->(corrector) { corrector.insert_before(node.source_range , "_(")
111
+ ->(corrector) {
112
+ corrector.insert_before(node.source_range , "_(")
87
113
  corrector.insert_after(node.source_range , ")") }
88
114
  end
89
115
 
90
- def multiline_string_correct(node)
116
+ def interpolation_correct(node)
117
+ interpolated_values_string = ""
118
+ count = 0
119
+ ->(corrector) {
120
+ node.children.each do |child|
121
+ # dstrs are split into "str" segments and other segments.
122
+ # The "other" segments are the interpolated values.
123
+ if child.type == :begin
124
+ value = child.children[0]
125
+ hash_key = "value"
126
+ if value.type == :lvar
127
+ # Use the variable's name as the format key
128
+ hash_key = value.loc.name.source
129
+ else
130
+ # These are placeholders that will manually need to be given
131
+ # a descriptive name
132
+ hash_key << "#{count}"
133
+ count += 1
134
+ end
135
+ if interpolated_values_string.empty?
136
+ interpolated_values_string << "{ "
137
+ end
138
+ interpolated_values_string << "#{hash_key}: #{value.loc.expression.source}, "
139
+
140
+ # Replace interpolation with format string
141
+ corrector.replace(child.loc.expression, "%{#{hash_key}}")
142
+ end
143
+ end
144
+ if !interpolated_values_string.empty?
145
+ interpolated_values_string << "}"
146
+ end
147
+ corrector.insert_before(node.source_range, '_(')
148
+ corrector.insert_after(node.source_range, ") % #{interpolated_values_string}")
149
+ }
91
150
  end
151
+
92
152
  end
93
153
  end
94
154
  end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tempfile'
4
+
5
+ # This module provides methods that make it easier to test Cops.
6
+ module CopHelper
7
+ extend RSpec::SharedContext
8
+
9
+ let(:ruby_version) { 2.2 }
10
+ let(:enabled_rails) { false }
11
+ let(:rails_version) { false }
12
+
13
+ def inspect_source_file(source)
14
+ Tempfile.open('tmp') { |f| inspect_source(source, f) }
15
+ end
16
+
17
+ def inspect_gemfile(source)
18
+ inspect_source(source, 'Gemfile')
19
+ end
20
+
21
+ def inspect_source(source, file = nil)
22
+ if source.is_a?(Array) && source.size == 1
23
+ raise "Don't use an array for a single line of code: #{source}"
24
+ end
25
+ RuboCop::Formatter::DisabledConfigFormatter.config_to_allow_offenses = {}
26
+ RuboCop::Formatter::DisabledConfigFormatter.detected_styles = {}
27
+ processed_source = parse_source(source, file)
28
+ raise 'Error parsing example code' unless processed_source.valid_syntax?
29
+ _investigate(cop, processed_source)
30
+ end
31
+
32
+ def parse_source(source, file = nil)
33
+ source = source.join($RS) if source.is_a?(Array)
34
+
35
+ if file && file.respond_to?(:write)
36
+ file.write(source)
37
+ file.rewind
38
+ file = file.path
39
+ end
40
+
41
+ RuboCop::ProcessedSource.new(source, ruby_version, file)
42
+ end
43
+
44
+ def autocorrect_source_file(source)
45
+ Tempfile.open('tmp') { |f| autocorrect_source(source, f) }
46
+ end
47
+
48
+ def autocorrect_source(source, file = nil)
49
+ cop.instance_variable_get(:@options)[:auto_correct] = true
50
+ processed_source = parse_source(source, file)
51
+ _investigate(cop, processed_source)
52
+
53
+ corrector =
54
+ RuboCop::Cop::Corrector.new(processed_source.buffer, cop.corrections)
55
+ corrector.rewrite
56
+ end
57
+
58
+ def autocorrect_source_with_loop(source, file = nil)
59
+ loop do
60
+ cop.instance_variable_set(:@corrections, [])
61
+ new_source = autocorrect_source(source, file)
62
+ return new_source if new_source == source
63
+ source = new_source
64
+ end
65
+ end
66
+
67
+ def _investigate(cop, processed_source)
68
+ forces = RuboCop::Cop::Force.all.each_with_object([]) do |klass, instances|
69
+ next unless cop.join_force?(klass)
70
+ instances << klass.new([cop])
71
+ end
72
+
73
+ commissioner =
74
+ RuboCop::Cop::Commissioner.new([cop], forces, raise_error: true)
75
+ commissioner.investigate(processed_source)
76
+ commissioner
77
+ end
78
+ end
79
+
80
+ module RuboCop
81
+ module Cop
82
+ # Monkey-patch Cop for tests to provide easy access to messages and
83
+ # highlights.
84
+ class Cop
85
+ def messages
86
+ offenses.sort.map(&:message)
87
+ end
88
+
89
+ def highlights
90
+ offenses.sort.map { |o| o.location.source }
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ RSpec.configure do |config|
97
+ config.include CopHelper
98
+ end
@@ -4,13 +4,13 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "rubocop-i18n"
7
- spec.version = '0.0.1'
8
- spec.authors = ["Brandon High"]
9
- spec.email = ["brandon.high@puppet.com"]
7
+ spec.version = '1.0.0'
8
+ spec.authors = ["Brandon High", "TP Honey", "Helen Campbell"]
9
+ spec.email = ["brandon.high@puppet.com", "tp@puppet.com", "helen@puppet.com"]
10
10
 
11
11
  spec.summary = %q{RuboCop rules for i18n}
12
12
  spec.description = %q{RuboCop rules for detecting and autocorrecting undecorated strings for i18n}
13
- spec.homepage = "https://github.com/highb/rubocop-i18n"
13
+ spec.homepage = "https://github.com/puppetlabs/rubocop-i18n"
14
14
  spec.license = 'Apache-2'
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
@@ -23,6 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "bundler", "~> 1.14"
24
24
  spec.add_development_dependency "rake", "~> 10.0"
25
25
  spec.add_development_dependency "rspec", "~> 3.0"
26
+ spec.add_development_dependency "rb-readline"
26
27
  spec.add_development_dependency "pry"
27
28
  spec.add_runtime_dependency "rubocop", "~> 0.49"
28
29
  end
metadata CHANGED
@@ -1,14 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-i18n
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandon High
8
+ - TP Honey
9
+ - Helen Campbell
8
10
  autorequire:
9
11
  bindir: exe
10
12
  cert_chain: []
11
- date: 2017-08-11 00:00:00.000000000 Z
13
+ date: 2017-09-06 00:00:00.000000000 Z
12
14
  dependencies:
13
15
  - !ruby/object:Gem::Dependency
14
16
  name: bundler
@@ -52,6 +54,20 @@ dependencies:
52
54
  - - "~>"
53
55
  - !ruby/object:Gem::Version
54
56
  version: '3.0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: rb-readline
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
55
71
  - !ruby/object:Gem::Dependency
56
72
  name: pry
57
73
  requirement: !ruby/object:Gem::Requirement
@@ -84,14 +100,16 @@ description: RuboCop rules for detecting and autocorrecting undecorated strings
84
100
  i18n
85
101
  email:
86
102
  - brandon.high@puppet.com
103
+ - tp@puppet.com
104
+ - helen@puppet.com
87
105
  executables: []
88
106
  extensions: []
89
107
  extra_rdoc_files: []
90
108
  files:
91
109
  - ".gitignore"
110
+ - ".travis.yml"
92
111
  - CODE_OF_CONDUCT.md
93
112
  - Gemfile
94
- - Gemfile.lock
95
113
  - LICENSE
96
114
  - README.md
97
115
  - Rakefile
@@ -102,8 +120,9 @@ files:
102
120
  - lib/rubocop/cop/i18n/gettext.rb
103
121
  - lib/rubocop/cop/i18n/gettext/decorate_function_message.rb
104
122
  - lib/rubocop/cop/i18n/gettext/decorate_string.rb
123
+ - lib/rubocop/rspec/cop_helper.rb
105
124
  - rubocop-i18n.gemspec
106
- homepage: https://github.com/highb/rubocop-i18n
125
+ homepage: https://github.com/puppetlabs/rubocop-i18n
107
126
  licenses:
108
127
  - Apache-2
109
128
  metadata: {}
@@ -1,60 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- rubocop-i18n (0.0.1)
5
- rubocop (~> 0.49)
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- ast (2.3.0)
11
- coderay (1.1.1)
12
- diff-lcs (1.3)
13
- method_source (0.8.2)
14
- parallel (1.12.0)
15
- parser (2.4.0.0)
16
- ast (~> 2.2)
17
- powerpack (0.1.1)
18
- pry (0.10.4)
19
- coderay (~> 1.1.0)
20
- method_source (~> 0.8.1)
21
- slop (~> 3.4)
22
- rainbow (2.2.2)
23
- rake
24
- rake (10.5.0)
25
- rspec (3.6.0)
26
- rspec-core (~> 3.6.0)
27
- rspec-expectations (~> 3.6.0)
28
- rspec-mocks (~> 3.6.0)
29
- rspec-core (3.6.0)
30
- rspec-support (~> 3.6.0)
31
- rspec-expectations (3.6.0)
32
- diff-lcs (>= 1.2.0, < 2.0)
33
- rspec-support (~> 3.6.0)
34
- rspec-mocks (3.6.0)
35
- diff-lcs (>= 1.2.0, < 2.0)
36
- rspec-support (~> 3.6.0)
37
- rspec-support (3.6.0)
38
- rubocop (0.49.1)
39
- parallel (~> 1.10)
40
- parser (>= 2.3.3.1, < 3.0)
41
- powerpack (~> 0.1)
42
- rainbow (>= 1.99.1, < 3.0)
43
- ruby-progressbar (~> 1.7)
44
- unicode-display_width (~> 1.0, >= 1.0.1)
45
- ruby-progressbar (1.8.1)
46
- slop (3.6.0)
47
- unicode-display_width (1.3.0)
48
-
49
- PLATFORMS
50
- ruby
51
-
52
- DEPENDENCIES
53
- bundler (~> 1.14)
54
- pry
55
- rake (~> 10.0)
56
- rspec (~> 3.0)
57
- rubocop-i18n!
58
-
59
- BUNDLED WITH
60
- 1.14.6