synvert-core 1.5.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71ae91a324677995dbfd9b1251ebf2a0a01e7c25289dcebcf86abb07599b1db8
4
- data.tar.gz: 4370235d14aee70f57b6b02313637e9d24b13ca2aee61b71e4fa4022f78bc3f8
3
+ metadata.gz: 4e0e8e77a7f85aa23300c28aaf44fca1d6b0a7ffad9177bc9925cfd7541f4e65
4
+ data.tar.gz: eb1b9267dbf8264a9c5978dd019dc0c26fcc6613cf0413b767c8fdf4c763cb00
5
5
  SHA512:
6
- metadata.gz: 8bcd68a69575a6cd5c5c0bc7a91f82d09d2f438ab06988c01ad14c7d97c863bc2556f5c3ad7d054e35cfcb830cb778ec10a9797952a56a9391062b4bf3ce2a3f
7
- data.tar.gz: be7b4c9fda8428c0d2a8c29350f6d2c60c40577c0f990ed5d8f4d2742fc2545fc8ddbd139298aaf39a9e69e9e9bf83fe29385745ac7ef10459da836572df9327
6
+ metadata.gz: 26a85edf49b07f5afc0688152bf63bf65966e72f15d58da615b8e4b6575e28a74978113a9dbbed78f0cf076e0d0a5d54d486ef83af6a46a0ce7e1b69547b33c1
7
+ data.tar.gz: 21bb822480bd6ea79725d27eabb48c3306da71dec81fb817c8d94027b7457e5ae1ed3d78d9d5d870c103f173e2a3bc00e8b45273c66bbd4dfcb259e2ada254bc
@@ -15,7 +15,6 @@ on:
15
15
 
16
16
  jobs:
17
17
  test:
18
-
19
18
  runs-on: ubuntu-latest
20
19
  strategy:
21
20
  matrix:
data/.gitignore CHANGED
@@ -3,7 +3,6 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
- Gemfile.lock
7
6
  InstalledFiles
8
7
  _yardoc
9
8
  coverage
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.7.0 (2022-09-16)
4
+
5
+ * Add `Rewriter#test`
6
+ * Use option `run_instance` instead of `sandbox`
7
+
8
+ ## 1.6.0 (2022-09-15)
9
+
10
+ * Make use of `NodeQuery` to query nodes
11
+ * Remove `Node#to_hash`
12
+ * Downgrade `activesupport` to < 7.0.0
13
+
3
14
  ## 1.5.0 (2022-07-02)
4
15
 
5
16
  * Abstract `node_query`
data/Gemfile CHANGED
@@ -5,6 +5,8 @@ source 'https://rubygems.org'
5
5
  # Specify your gem's dependencies in synvert.gemspec
6
6
  gemspec
7
7
 
8
+ require 'pp' # https://github.com/defunkt/fakefs/issues/99
9
+ gem "fakefs", require: "fakefs/safe"
8
10
  gem "guard"
9
11
  gem "guard-rspec"
10
12
  gem "rake"
data/Gemfile.lock ADDED
@@ -0,0 +1,103 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ synvert-core (1.7.0)
5
+ activesupport (< 7.0.0)
6
+ erubis
7
+ node_mutation
8
+ node_query
9
+ parser
10
+ parser_node_ext
11
+
12
+ GEM
13
+ remote: https://rubygems.org/
14
+ specs:
15
+ activesupport (6.1.7)
16
+ concurrent-ruby (~> 1.0, >= 1.0.2)
17
+ i18n (>= 1.6, < 2)
18
+ minitest (>= 5.1)
19
+ tzinfo (~> 2.0)
20
+ zeitwerk (~> 2.3)
21
+ ast (2.4.2)
22
+ coderay (1.1.3)
23
+ concurrent-ruby (1.1.10)
24
+ diff-lcs (1.5.0)
25
+ erubis (2.7.0)
26
+ fakefs (1.8.0)
27
+ ffi (1.15.5)
28
+ formatador (1.1.0)
29
+ guard (2.18.0)
30
+ formatador (>= 0.2.4)
31
+ listen (>= 2.7, < 4.0)
32
+ lumberjack (>= 1.0.12, < 2.0)
33
+ nenv (~> 0.1)
34
+ notiffany (~> 0.0)
35
+ pry (>= 0.13.0)
36
+ shellany (~> 0.0)
37
+ thor (>= 0.18.1)
38
+ guard-compat (1.2.1)
39
+ guard-rspec (4.7.3)
40
+ guard (~> 2.1)
41
+ guard-compat (~> 1.1)
42
+ rspec (>= 2.99.0, < 4.0)
43
+ i18n (1.12.0)
44
+ concurrent-ruby (~> 1.0)
45
+ listen (3.7.1)
46
+ rb-fsevent (~> 0.10, >= 0.10.3)
47
+ rb-inotify (~> 0.9, >= 0.9.10)
48
+ lumberjack (1.2.8)
49
+ method_source (1.0.0)
50
+ minitest (5.16.3)
51
+ nenv (0.3.0)
52
+ node_mutation (1.3.3)
53
+ activesupport (< 7.0.0)
54
+ erubis
55
+ node_query (1.6.0)
56
+ activesupport (< 7.0.0)
57
+ notiffany (0.1.3)
58
+ nenv (~> 0.1)
59
+ shellany (~> 0.0)
60
+ parser (3.1.2.1)
61
+ ast (~> 2.4.1)
62
+ parser_node_ext (0.4.0)
63
+ parser
64
+ pry (0.14.1)
65
+ coderay (~> 1.1)
66
+ method_source (~> 1.0)
67
+ rake (13.0.6)
68
+ rb-fsevent (0.11.1)
69
+ rb-inotify (0.10.1)
70
+ ffi (~> 1.0)
71
+ rspec (3.10.0)
72
+ rspec-core (~> 3.10.0)
73
+ rspec-expectations (~> 3.10.0)
74
+ rspec-mocks (~> 3.10.0)
75
+ rspec-core (3.10.2)
76
+ rspec-support (~> 3.10.0)
77
+ rspec-expectations (3.10.2)
78
+ diff-lcs (>= 1.2.0, < 2.0)
79
+ rspec-support (~> 3.10.0)
80
+ rspec-mocks (3.10.2)
81
+ diff-lcs (>= 1.2.0, < 2.0)
82
+ rspec-support (~> 3.10.0)
83
+ rspec-support (3.10.3)
84
+ shellany (0.0.1)
85
+ thor (1.2.1)
86
+ tzinfo (2.0.5)
87
+ concurrent-ruby (~> 1.0)
88
+ zeitwerk (2.6.0)
89
+
90
+ PLATFORMS
91
+ ruby
92
+
93
+ DEPENDENCIES
94
+ fakefs
95
+ guard
96
+ guard-rspec
97
+ rake
98
+ rspec
99
+ rspec-mocks
100
+ synvert-core!
101
+
102
+ BUNDLED WITH
103
+ 2.3.7
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # synvert-core-ruby
2
2
 
3
- <img src="https://synvert.xinminlabs.com/img/logo_96.png" alt="logo" width="32" height="32" />
3
+ <img src="https://synvert.net/img/logo_96.png" alt="logo" width="32" height="32" />
4
4
 
5
5
  [![AwesomeCode Status for xinminlabs/synvert-core-ruby](https://awesomecode.io/projects/033f7f02-7b22-41c3-a902-fca37f1ec72a/status)](https://awesomecode.io/repos/xinminlabs/synvert-core-ruby)
6
6
  ![Main workflow](https://github.com/xinminlabs/synvert-core-ruby/actions/workflows/main.yml/badge.svg)
@@ -50,43 +50,6 @@ module Parser::AST
50
50
  loc.expression.line
51
51
  end
52
52
 
53
- # Match node with rules.
54
- # It provides some additional keywords to match rules, +any+, +contain+, +not+, +in+, +not_in+, +gt+, +gte+, +lt+, +lte+.
55
- # @example
56
- # type: 'send', arguments: { any: 'Lifo::ShowExceptions' }
57
- # type: { in: ['send', 'csend'] }
58
- # type: :send, arguments: { length: { gt: 2 } }
59
- # @param rules [Hash] rules to match.
60
- # @return true if matches.
61
- def match?(rules)
62
- keywords = %i[any contain not in not_in gt gte lt lte]
63
- flat_hash(rules).keys.all? do |multi_keys|
64
- last_key = multi_keys.last
65
- actual = keywords.include?(last_key) ? actual_value(multi_keys[0...-1]) : actual_value(multi_keys)
66
- expected = expected_value(rules, multi_keys)
67
- case last_key
68
- when :any, :contain
69
- actual.any? { |actual_value| match_value?(actual_value, expected) }
70
- when :not
71
- !match_value?(actual, expected)
72
- when :in
73
- expected.any? { |expected_value| match_value?(actual, expected_value) }
74
- when :not_in
75
- expected.all? { |expected_value| !match_value?(actual, expected_value) }
76
- when :gt
77
- actual > expected
78
- when :gte
79
- actual >= expected
80
- when :lt
81
- actual < expected
82
- when :lte
83
- actual <= expected
84
- else
85
- match_value?(actual, expected)
86
- end
87
- end
88
- end
89
-
90
53
  # Strip curly braces for hash.
91
54
  # @example
92
55
  # node # s(:hash, s(:pair, s(:sym, :foo), s(:str, "bar")))
@@ -161,141 +124,5 @@ module Parser::AST
161
124
  to_source
162
125
  end
163
126
  end
164
-
165
- # Convert node to a hash, so that it can be converted to a json.
166
- def to_hash
167
- result = { type: type }
168
- if TYPE_CHILDREN[type]
169
- TYPE_CHILDREN[type].each do |key|
170
- value = send(key)
171
- result[key] =
172
- case value
173
- when Array
174
- value.map { |v| v.respond_to?(:to_hash) ? v.to_hash : v }
175
- when Parser::AST::Node
176
- value.to_hash
177
- else
178
- value
179
- end
180
- end
181
- else
182
- result[:children] = children.map { |c| c.respond_to?(:to_hash) ? c.to_hash : c }
183
- end
184
- result
185
- end
186
-
187
- private
188
-
189
- # Compare actual value with expected value.
190
- #
191
- # @param actual [Object] actual value.
192
- # @param expected [Object] expected value.
193
- # @return [Boolean]
194
- # @raise [Synvert::Core::MethodNotSupported] if expected class is not supported.
195
- def match_value?(actual, expected)
196
- return true if actual == expected
197
-
198
- case expected
199
- when Symbol
200
- if actual.is_a?(Parser::AST::Node)
201
- actual.to_source == ":#{expected}" || actual.to_source == expected.to_s
202
- else
203
- actual.to_sym == expected
204
- end
205
- when String
206
- if actual.is_a?(Parser::AST::Node)
207
- actual.to_source == expected || actual.to_source == unwrap_quote(expected) ||
208
- unwrap_quote(actual.to_source) == expected || unwrap_quote(actual.to_source) == unwrap_quote(expected)
209
- else
210
- actual.to_s == expected || wrap_quote(actual.to_s) == expected
211
- end
212
- when Regexp
213
- if actual.is_a?(Parser::AST::Node)
214
- actual.to_source =~ Regexp.new(expected.to_s, Regexp::MULTILINE)
215
- else
216
- actual.to_s =~ Regexp.new(expected.to_s, Regexp::MULTILINE)
217
- end
218
- when Array
219
- return false unless expected.length == actual.length
220
-
221
- actual.zip(expected).all? { |a, e| match_value?(a, e) }
222
- when NilClass
223
- if actual.is_a?(Parser::AST::Node)
224
- :nil == actual.type
225
- else
226
- actual.nil?
227
- end
228
- when Numeric
229
- if actual.is_a?(Parser::AST::Node)
230
- actual.children[0] == expected
231
- else
232
- actual == expected
233
- end
234
- when TrueClass
235
- :true == actual.type
236
- when FalseClass
237
- :false == actual.type
238
- when Parser::AST::Node
239
- actual == expected
240
- when Synvert::Core::Rewriter::AnyValue
241
- !actual.nil?
242
- else
243
- raise Synvert::Core::MethodNotSupported, "#{expected.class} is not handled for match_value?"
244
- end
245
- end
246
-
247
- # Convert a hash to flat one.
248
- #
249
- # @example
250
- # flat_hash(type: 'block', caller: {type: 'send', receiver: 'RSpec'})
251
- # # {[:type] => 'block', [:caller, :type] => 'send', [:caller, :receiver] => 'RSpec'}
252
- # @param h [Hash] original hash.
253
- # @return flatten hash.
254
- def flat_hash(h, k = [])
255
- new_hash = {}
256
- h.each_pair do |key, val|
257
- if val.is_a?(Hash)
258
- new_hash.merge!(flat_hash(val, k + [key]))
259
- else
260
- new_hash[k + [key]] = val
261
- end
262
- end
263
- new_hash
264
- end
265
-
266
- # Get actual value from the node.
267
- #
268
- # @param multi_keys [Array<Symbol, String>]
269
- # @return [Object] actual value.
270
- def actual_value(multi_keys)
271
- multi_keys.inject(self) { |n, key| n.send(key) if n }
272
- end
273
-
274
- # Get expected value from rules.
275
- #
276
- # @param rules [Hash]
277
- # @param multi_keys [Array<Symbol>]
278
- # @return [Object] expected value.
279
- def expected_value(rules, multi_keys)
280
- multi_keys.inject(rules) { |o, key| o[key] }
281
- end
282
-
283
- # Wrap the string with single or double quote.
284
- def wrap_quote(string)
285
- if string.include?("'")
286
- "\"#{string}\""
287
- else
288
- "'#{string}'"
289
- end
290
- end
291
-
292
- # Unwrap the quote from the string.
293
- def unwrap_quote(string)
294
- if (string[0] == '"' && string[-1] == '"') || (string[0] == "'" && string[-1] == "'")
295
- string[1...-1]
296
- else
297
- string
298
- end
299
- end
300
127
  end
301
128
  end
@@ -9,11 +9,7 @@ module Synvert::Core
9
9
  #
10
10
  # @return [Boolean]
11
11
  def match?
12
- match = false
13
- NodeQuery::Helper.handle_recursive_child(@instance.current_node) do |child_node|
14
- match ||= child_node&.match?(@rules)
15
- end
16
- match
12
+ @node_query.query_nodes(target_node, including_self: false, stop_at_first_match: true).size > 0
17
13
  end
18
14
  end
19
15
  end
@@ -9,7 +9,7 @@ module Synvert::Core
9
9
  #
10
10
  # @return [Boolean]
11
11
  def match?
12
- @instance.current_node.body.size == 1 && @instance.current_node.body.first.match?(@rules)
12
+ target_node.body.size == 1 && @node_query.match_node?(target_node.body.first)
13
13
  end
14
14
  end
15
15
  end
@@ -9,11 +9,7 @@ module Synvert::Core
9
9
  #
10
10
  # return [Boolean]
11
11
  def match?
12
- match = false
13
- NodeQuery::Helper.handle_recursive_child(@instance.current_node) do |child_node|
14
- match ||= child_node&.match?(@rules)
15
- end
16
- !match
12
+ @node_query.query_nodes(target_node, including_self: false, stop_at_first_match: true).size == 0
17
13
  end
18
14
  end
19
15
  end
@@ -10,7 +10,7 @@ module Synvert::Core
10
10
  # @yield run when condition matches
11
11
  def initialize(instance, rules, &block)
12
12
  @instance = instance
13
- @rules = rules
13
+ @node_query = NodeQuery.new(rules)
14
14
  @block = block
15
15
  end
16
16
 
@@ -27,5 +27,9 @@ module Synvert::Core
27
27
  def match?
28
28
  raise NotImplementedError, 'must be implemented by subclasses'
29
29
  end
30
+
31
+ def target_node
32
+ @instance.current_node
33
+ end
30
34
  end
31
35
  end
@@ -30,7 +30,7 @@ module Synvert::Core
30
30
 
31
31
  # Process the instance.
32
32
  # It finds specified files, for each file, it executes the block code, rewrites the original code,
33
- # then write the code back to the original file.
33
+ # then writes the code back to the original file.
34
34
  def process
35
35
  @file_patterns.each do |file_pattern|
36
36
  Dir.glob(File.join(Configuration.path, file_pattern)).each do |file_path|
@@ -41,6 +41,21 @@ module Synvert::Core
41
41
  end
42
42
  end
43
43
 
44
+ # Test the instance.
45
+ # It finds specified files, for each file, it executes the block code, tests the original code,
46
+ # then returns the actions.
47
+ def test
48
+ paths = @file_patterns.flat_map do |file_pattern|
49
+ Dir.glob(File.join(Configuration.path, file_pattern))
50
+ end
51
+
52
+ paths.uniq.map do |file_path|
53
+ next if Configuration.skip_files.include?(file_path)
54
+
55
+ test_file(file_path)
56
+ end
57
+ end
58
+
44
59
  # Gets current node, it allows to get current node in block code.
45
60
  #
46
61
  # @return [Parser::AST::Node]
@@ -79,11 +94,13 @@ module Synvert::Core
79
94
  # # matches FactoryBot.create(:user)
80
95
  # find_node '.send[receiver=FactoryBot][message=create][arguments.size=1]' do
81
96
  # end
82
- # @param query_string [String] query string to find matching ast nodes.
97
+ # @param nql [String] node query language to find matching ast nodes.
83
98
  # @yield run on the matching nodes.
84
99
  # @raise [Synvert::Core::NodeQuery::Compiler::ParseError] if query string is invalid.
85
- def find_node(query_string, &block)
86
- Rewriter::QueryScope.new(self, query_string, &block).process
100
+ def find_node(nql, options = {}, &block)
101
+ Rewriter::QueryScope.new(self, nql, options, &block).process
102
+ rescue NodeQueryLexer::ScanError, Racc::ParseError => e
103
+ raise NodeQuery::Compiler::ParseError, "Invalid query string: #{nql}"
87
104
  end
88
105
 
89
106
  # Parse +within_node+ dsl, it creates a {Synvert::Core::Rewriter::WithinScope} to recursively find matching ast nodes,
@@ -94,12 +111,11 @@ module Synvert::Core
94
111
  # end
95
112
  # @param rules [Hash] rules to find mathing ast nodes.
96
113
  # @param options [Hash] optional
97
- # @option stop_when_match [Boolean] set if stop when match, default is false
98
- # @option direct [Boolean] set if find direct matching ast nodes, default is false
114
+ # @option including_self [Boolean] set if query the current node, default is true
115
+ # @option stop_at_first_match [Boolean] set if stop at first match, default is false
116
+ # @option recursive [Boolean] set if recursively query child nodes, default is true
99
117
  # @yield run on the matching nodes.
100
118
  def within_node(rules, options = {}, &block)
101
- options[:stop_when_match] ||= false
102
- options[:direct] ||= false
103
119
  Rewriter::WithinScope.new(self, rules, options, &block).process
104
120
  end
105
121
 
@@ -377,6 +393,37 @@ module Synvert::Core
377
393
  end
378
394
  end
379
395
 
396
+ # Test one file.
397
+ #
398
+ # @param file_path [String]
399
+ def test_file(file_path)
400
+ @current_file = file_path
401
+ source = read_source(file_path)
402
+ @current_mutation = NodeMutation.new(source)
403
+ begin
404
+ node = parse_code(file_path, source)
405
+
406
+ process_with_node(node) do
407
+ instance_eval(&@block)
408
+ rescue NoMethodError => e
409
+ puts [
410
+ "error: #{e.message}",
411
+ "file: #{file_path}",
412
+ "source: #{source}",
413
+ "line: #{current_node.line}"
414
+ ].join("\n")
415
+ raise
416
+ end
417
+
418
+ result = @current_mutation.test
419
+ result.file_path = file_path
420
+ result
421
+ rescue Parser::SyntaxError
422
+ puts "[Warn] file #{file_path} was not parsed correctly."
423
+ # do nothing, iterate next file
424
+ end
425
+ end
426
+
380
427
  # Read file source.
381
428
  # @param file_path [String] file path
382
429
  # @return [String] file source
@@ -6,11 +6,14 @@ module Synvert::Core
6
6
  # Initialize a QueryScope.
7
7
  #
8
8
  # @param instance [Synvert::Core::Rewriter::Instance]
9
- # @param query_string [String]
9
+ # @param nql [String]
10
+ # @param options [Hash]
10
11
  # @yield run on all matching nodes
11
- def initialize(instance, query_string, &block)
12
+ def initialize(instance, nql, options = {}, &block)
12
13
  super(instance, &block)
13
- @query_string = query_string
14
+
15
+ @options = { including_self: true, stop_at_first_match: false, recursive: true }.merge(options)
16
+ @node_query = NodeQuery.new(nql)
14
17
  end
15
18
 
16
19
  # Find out the matching nodes.
@@ -22,15 +25,14 @@ module Synvert::Core
22
25
  current_node = @instance.current_node
23
26
  return unless current_node
24
27
 
28
+ matching_nodes = @node_query.query_nodes(current_node, @options)
25
29
  @instance.process_with_node(current_node) do
26
- NodeQuery.new(@query_string).parse(current_node).each do |node|
30
+ matching_nodes.each do |node|
27
31
  @instance.process_with_node(node) do
28
32
  @instance.instance_eval(&@block)
29
33
  end
30
34
  end
31
35
  end
32
- rescue NodeQueryLexer::ScanError, Racc::ParseError => e
33
- raise NodeQuery::Compiler::ParseError, "Invalid query string: #{@query_string}"
34
36
  end
35
37
  end
36
38
  end
@@ -11,8 +11,9 @@ module Synvert::Core
11
11
  # @yield run on all matching nodes
12
12
  def initialize(instance, rules, options = {}, &block)
13
13
  super(instance, &block)
14
- @rules = rules
15
- @options = options
14
+
15
+ @options = { including_self: true, stop_at_first_match: false, recursive: true }.merge(options)
16
+ @node_query = NodeQuery.new(rules)
16
17
  end
17
18
 
18
19
  # Find out the matching nodes.
@@ -22,14 +23,7 @@ module Synvert::Core
22
23
  current_node = @instance.current_node
23
24
  return unless current_node
24
25
 
25
- matching_nodes =
26
- if @options[:direct]
27
- find_direct_matching_nodes(current_node)
28
- elsif @options[:stop_when_match]
29
- find_matching_nodes(current_node)
30
- else
31
- find_recursive_matching_nodes(current_node)
32
- end
26
+ matching_nodes = @node_query.query_nodes(current_node, @options)
33
27
  @instance.process_with_node current_node do
34
28
  matching_nodes.each do |matching_node|
35
29
  @instance.process_with_node matching_node do
@@ -38,82 +32,5 @@ module Synvert::Core
38
32
  end
39
33
  end
40
34
  end
41
-
42
- private
43
-
44
- # Find the matching nodes only in current or direct children.
45
- #
46
- # @param current_node [Parser::AST::Node]
47
- def find_direct_matching_nodes(current_node)
48
- matching_nodes = []
49
- if current_node.is_a?(Parser::AST::Node)
50
- if current_node.type == :begin
51
- current_node.children.each do |child_node|
52
- matching_nodes << child_node if child_node.match?(@rules)
53
- end
54
- elsif current_node.match?(@rules)
55
- matching_nodes << current_node
56
- end
57
- else
58
- current_node.each do |child_node|
59
- matching_nodes << child_node if child_node.match?(@rules)
60
- end
61
- end
62
- matching_nodes
63
- end
64
-
65
- # Find matching nodes in all recursive children.
66
- #
67
- # @param current_node [Parser::AST::Node]
68
- def find_recursive_matching_nodes(current_node)
69
- matching_nodes = []
70
- if current_node.is_a?(Parser::AST::Node)
71
- matching_nodes << current_node if current_node.match?(@rules)
72
- NodeQuery::Helper.handle_recursive_child(current_node) do |child_node|
73
- matching_nodes << child_node if child_node.match?(@rules)
74
- end
75
- else
76
- current_node.each do |node|
77
- matching_nodes << node if node.match?(@rules)
78
- NodeQuery::Helper.handle_recursive_child(node) do |child_node|
79
- matching_nodes << child_node if child_node.match?(@rules)
80
- end
81
- end
82
- end
83
- matching_nodes
84
- end
85
-
86
- # Find matching nodes in recursive children but do not continue on matching nodes.
87
- #
88
- # @param current_node [Parser::AST::Node]
89
- def find_matching_nodes(current_node)
90
- matching_nodes = []
91
- if current_node.is_a?(Parser::AST::Node)
92
- if current_node.match?(@rules)
93
- matching_nodes << current_node
94
- return matching_nodes
95
- end
96
- NodeQuery::Helper.handle_recursive_child(current_node) do |child_node|
97
- if child_node.match?(@rules)
98
- matching_nodes << child_node
99
- next :stop
100
- end
101
- end
102
- else
103
- current_node.each do |node|
104
- if node.match?(@rules)
105
- matching_nodes << node
106
- next
107
- end
108
- NodeQuery::Helper.handle_recursive_child(node) do |child_node|
109
- if child_node.match?(@rules)
110
- matching_nodes << child_node
111
- next :stop
112
- end
113
- end
114
- end
115
- end
116
- matching_nodes
117
- end
118
35
  end
119
36
  end
@@ -72,15 +72,16 @@ module Synvert::Core
72
72
  #
73
73
  # @param group [String] the rewriter group.
74
74
  # @param name [String] the rewriter name.
75
- # @param sandbox [Boolean] if run in sandbox mode.
75
+ # @param options [Hash]
76
+ # @option options [Boolean] :run_instance (true) process the instance.
76
77
  # @return [Synvert::Core::Rewriter] the registered rewriter.
77
78
  # @raise [Synvert::Core::RewriterNotFound] if the registered rewriter is not found.
78
- def call(group, name, sandbox = false)
79
+ def call(group, name, options = {})
79
80
  rewriter = fetch(group, name)
80
- if sandbox
81
- rewriter.process_with_sandbox
82
- else
81
+ if options[:run_instance]
83
82
  rewriter.process
83
+ else
84
+ rewriter.process_with_sandbox
84
85
  end
85
86
  rewriter
86
87
  end
@@ -137,6 +138,8 @@ module Synvert::Core
137
138
  @warnings = []
138
139
  @affected_files = Set.new
139
140
  @redo_until_no_change = false
141
+ @options = { run_instance: true, write_to_file: true }
142
+ @test_results = []
140
143
  self.class.register(@group, @name, self)
141
144
  end
142
145
 
@@ -152,14 +155,29 @@ module Synvert::Core
152
155
  # Process rewriter with sandbox mode.
153
156
  # It will call the block but doesn't change any file.
154
157
  def process_with_sandbox
155
- @sandbox = true
158
+ @options[:run_instance] = false
156
159
  begin
157
160
  process
158
161
  ensure
159
- @sandbox = false
162
+ @options[:run_instance] = true
160
163
  end
161
164
  end
162
165
 
166
+ def test
167
+ @options[:write_to_file] = false
168
+ begin
169
+ @affected_files = Set.new
170
+ instance_eval(&@block)
171
+
172
+ if !@affected_files.empty? && @redo_until_no_change # redo
173
+ test
174
+ end
175
+ ensure
176
+ @options[:write_to_file] = true
177
+ end
178
+ @test_results
179
+ end
180
+
163
181
  # Add a warning.
164
182
  #
165
183
  # @param warning [Synvert::Core::Rewriter::Warning]
@@ -225,12 +243,18 @@ module Synvert::Core
225
243
  # @param file_patterns [String|Array<String>] string pattern or list of string pattern to find files, e.g. ['spec/**/*_spec.rb']
226
244
  # @param block [Block] the block to rewrite code in the matching files.
227
245
  def within_files(file_patterns, &block)
228
- return if @sandbox
246
+ return unless @options[:run_instance]
229
247
 
230
248
  return if @ruby_version && !@ruby_version.match?
231
249
  return if @gem_spec && !@gem_spec.match?
232
250
 
233
- Rewriter::Instance.new(self, Array(file_patterns), &block).process
251
+ instance = Rewriter::Instance.new(self, Array(file_patterns), &block)
252
+ if @options[:write_to_file]
253
+ instance.process
254
+ else
255
+ results = instance.test
256
+ @test_results += results.select { |result| result.affected? }
257
+ end
234
258
  end
235
259
 
236
260
  # Parse +within_file+ dsl, it finds a specifiled file.
@@ -248,7 +272,7 @@ module Synvert::Core
248
272
  # @param filename [String] file name of newly created file.
249
273
  # @param content [String] file body of newly created file.
250
274
  def add_file(filename, content)
251
- return if @sandbox
275
+ return unless @options[:run_instance]
252
276
 
253
277
  filepath = File.join(Configuration.path, filename)
254
278
  if File.exist?(filepath)
@@ -267,7 +291,7 @@ module Synvert::Core
267
291
  # end
268
292
  # @param filename [String] file name.
269
293
  def remove_file(filename)
270
- return if @sandbox
294
+ return unless @options[:run_instance]
271
295
 
272
296
  file_path = File.join(Configuration.path, filename)
273
297
  File.delete(file_path) if File.exist?(file_path)
@@ -293,7 +317,7 @@ module Synvert::Core
293
317
  # @param group [String] group of another rewriter.
294
318
  # @param name [String] name of another rewriter.
295
319
  def add_snippet(group, name)
296
- @sub_snippets << self.class.call(group.to_s, name.to_s, @sandbox)
320
+ @sub_snippets << self.class.call(group.to_s, name.to_s, @options)
297
321
  end
298
322
 
299
323
  # Parse +helper_method+ dsl, it defines helper method for {Synvert::Core::Rewriter::Instance}.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Synvert
4
4
  module Core
5
- VERSION = '1.5.0'
5
+ VERSION = '1.7.0'
6
6
  end
7
7
  end
data/spec/spec_helper.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
4
 
5
5
  require 'synvert/core'
6
+ require 'fakefs/spec_helpers'
6
7
 
7
8
  Dir[File.join(File.dirname(__FILE__), 'support', '*')].each do |path|
8
9
  require path
@@ -10,6 +11,7 @@ end
10
11
 
11
12
  RSpec.configure do |config|
12
13
  config.include ParserHelper
14
+ config.include FakeFS::SpecHelpers, fakefs: true
13
15
 
14
16
  config.run_all_when_everything_filtered = true
15
17
  config.filter_run :focus
@@ -3,133 +3,6 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Parser::AST::Node do
6
- describe '#match?' do
7
- it 'matches class name' do
8
- source = 'class Synvert; end'
9
- node = parse(source)
10
- expect(node).to be_match(type: 'class', name: 'Synvert')
11
- end
12
-
13
- it 'matches message with regexp' do
14
- source = 'User.find_by_login(login)'
15
- node = parse(source)
16
- expect(node).to be_match(type: 'send', message: /^find_by_/)
17
- end
18
-
19
- it 'matches arguments with symbol' do
20
- source = 'params[:user]'
21
- node = parse(source)
22
- expect(node).to be_match(type: 'send', receiver: 'params', message: '[]', arguments: [:user])
23
- end
24
-
25
- it 'matches pair key with symbol' do
26
- source = '{ type: :model }'
27
- node = parse(source).children[0]
28
- expect(node).to be_match(type: 'pair', key: :type)
29
- end
30
-
31
- it 'matches assign number' do
32
- source = 'at_least(0)'
33
- node = parse(source)
34
- expect(node).to be_match(type: 'send', arguments: [0])
35
- end
36
-
37
- it 'matches assign float' do
38
- source = 'at_least(1.5)'
39
- node = parse(source)
40
- expect(node).to be_match(type: 'send', arguments: [1.5])
41
- end
42
-
43
- it 'matches arguments with string' do
44
- source = 'params["user"]'
45
- node = parse(source)
46
- expect(node).to be_match(type: 'send', receiver: 'params', message: '[]', arguments: ['user'])
47
- end
48
-
49
- it 'matches arguments with string 2' do
50
- source = 'params["user"]'
51
- node = parse(source)
52
- expect(node).to be_match(type: 'send', receiver: 'params', message: '[]', arguments: ["'user'"])
53
- end
54
-
55
- it 'matches arguments with string 3' do
56
- source = "{ notice: 'Welcome' }"
57
- node = parse(source)
58
- expect(node).to be_match(type: 'hash', notice_value: "'Welcome'")
59
- end
60
-
61
- it 'matches arguments any' do
62
- source = 'config.middleware.insert_after ActiveRecord::QueryCache, Lifo::Cache, page_cache: false'
63
- node = parse(source)
64
- expect(node).to be_match(type: 'send', arguments: { any: 'Lifo::Cache' })
65
- end
66
-
67
- it 'matches arguments with nested hash' do
68
- source = '{ user_id: user.id }'
69
- node = parse(source)
70
- expect(node).to be_match(
71
- type: 'hash',
72
- user_id_value: {
73
- type: 'send',
74
- receiver: { type: 'send', message: 'user' },
75
- message: 'id'
76
- }
77
- )
78
- end
79
-
80
- it 'matches arguments contain' do
81
- source = 'def slow(foo, bar, &block); end'
82
- node = parse(source)
83
- expect(node).to be_match(type: 'def', arguments: { contain: '&block' })
84
- end
85
-
86
- it 'matches not' do
87
- source = 'class Synvert; end'
88
- node = parse(source)
89
- expect(node).not_to be_match(type: 'class', name: { not: 'Synvert' })
90
- end
91
-
92
- it 'matches in' do
93
- source = 'FactoryBot.create(:user)'
94
- node = parse(source)
95
- expect(node).to be_match(type: 'send', message: { in: %i[create build] })
96
- end
97
-
98
- it 'matches not_in' do
99
- source = 'FactoryBot.create(:user)'
100
- node = parse(source)
101
- expect(node).not_to be_match(type: 'send', message: { not_in: %i[create build] })
102
- end
103
-
104
- it 'matches gt' do
105
- source = 'foobar(foo, bar)'
106
- node = parse(source)
107
- expect(node).to be_match(type: 'send', arguments: { size: { gt: 1 } })
108
- expect(node).not_to be_match(type: 'send', arguments: { size: { gt: 2 } })
109
- end
110
-
111
- it 'matches gte' do
112
- source = 'foobar(foo, bar)'
113
- node = parse(source)
114
- expect(node).to be_match(type: 'send', arguments: { size: { gte: 2 } })
115
- expect(node).not_to be_match(type: 'send', arguments: { size: { gte: 3 } })
116
- end
117
-
118
- it 'matches lt' do
119
- source = 'foobar(foo, bar)'
120
- node = parse(source)
121
- expect(node).to be_match(type: 'send', arguments: { size: { lt: 3 } })
122
- expect(node).not_to be_match(type: 'send', arguments: { size: { lt: 2 } })
123
- end
124
-
125
- it 'matches lte' do
126
- source = 'foobar(foo, bar)'
127
- node = parse(source)
128
- expect(node).to be_match(type: 'send', arguments: { size: { lte: 2 } })
129
- expect(node).not_to be_match(type: 'send', arguments: { size: { lte: 1 } })
130
- end
131
- end
132
-
133
6
  describe '#strip_curly_braces' do
134
7
  context 'hash node' do
135
8
  it 'removes curly braces' do
@@ -232,47 +105,4 @@ describe Parser::AST::Node do
232
105
  end
233
106
  end
234
107
  end
235
-
236
- describe '#to_hash' do
237
- it 'gets hash' do
238
- node = parse(<<~EOS)
239
- class Synvert
240
- def foobar(foo, bar)
241
- { foo => bar }
242
- end
243
- end
244
- EOS
245
- expect(node.to_hash).to eq(
246
- {
247
- type: :class,
248
- parent_class: nil,
249
- name: {
250
- type: :const,
251
- parent_const: nil,
252
- name: :Synvert
253
- },
254
- body: [
255
- {
256
- type: :def,
257
- name: :foobar,
258
- arguments: [
259
- { type: :arg, name: :foo },
260
- { type: :arg, name: :bar }
261
- ],
262
- body: [
263
- {
264
- type: :hash,
265
- pairs: {
266
- type: :pair,
267
- key: { type: :lvar, name: :foo },
268
- value: { type: :lvar, name: :bar }
269
- }
270
- }
271
- ]
272
- }
273
- ]
274
- }
275
- )
276
- end
277
- end
278
108
  end
@@ -12,16 +12,27 @@ module Synvert::Core
12
12
  it 'parses find_node' do
13
13
  scope = double
14
14
  block = proc {}
15
- expect(Rewriter::QueryScope).to receive(:new).with(instance, '.send[message=create]', &block).and_return(scope)
15
+ expect(Rewriter::QueryScope).to receive(:new).with(instance, '.send[message=create]', {}, &block).and_return(scope)
16
16
  expect(scope).to receive(:process)
17
17
  instance.find_node('.send[message=create]', &block)
18
18
  end
19
19
 
20
+ it 'raises ParseError when parsing invalid nql' do
21
+ block = proc {}
22
+ expect {
23
+ instance.find_node('synvert', &block)
24
+ }.to raise_error(NodeQuery::Compiler::ParseError)
25
+
26
+ expect {
27
+ instance.find_node('.block <caller.arguments> .hash', &block)
28
+ }.to raise_error(NodeQuery::Compiler::ParseError)
29
+ end
30
+
20
31
  it 'parses within_node' do
21
32
  scope = double
22
33
  block = proc {}
23
34
  expect(Rewriter::WithinScope).to receive(:new)
24
- .with(instance, { type: 'send', message: 'create' }, { stop_when_match: false, direct: false }, &block)
35
+ .with(instance, { type: 'send', message: 'create' }, {}, &block)
25
36
  .and_return(scope)
26
37
  expect(scope).to receive(:process)
27
38
  instance.within_node(type: 'send', message: 'create', &block)
@@ -31,30 +42,20 @@ module Synvert::Core
31
42
  scope = double
32
43
  block = proc {}
33
44
  expect(Rewriter::WithinScope).to receive(:new)
34
- .with(instance, { type: 'send', message: 'create' }, { stop_when_match: false, direct: false }, &block)
45
+ .with(instance, { type: 'send', message: 'create' }, {}, &block)
35
46
  .and_return(scope)
36
47
  expect(scope).to receive(:process)
37
48
  instance.with_node(type: 'send', message: 'create', &block)
38
49
  end
39
50
 
40
- it 'parses within_node with stop_when_match true' do
51
+ it 'parses within_node with stop_at_first_match true' do
41
52
  scope = double
42
53
  block = proc {}
43
54
  expect(Rewriter::WithinScope).to receive(:new)
44
- .with(instance, { type: 'send', message: 'create' }, { stop_when_match: true, direct: false }, &block)
55
+ .with(instance, { type: 'send', message: 'create' }, { stop_at_first_match: true }, &block)
45
56
  .and_return(scope)
46
57
  expect(scope).to receive(:process)
47
- instance.within_node({ type: 'send', message: 'create' }, { stop_when_match: true }, &block)
48
- end
49
-
50
- it 'parses within_node with direct true' do
51
- scope = double
52
- block = proc {}
53
- expect(Rewriter::WithinScope).to receive(:new)
54
- .with(instance, { type: 'send', message: 'create' }, { stop_when_match: false, direct: true }, &block)
55
- .and_return(scope)
56
- expect(scope).to receive(:process)
57
- instance.within_node({ type: 'send', message: 'create' }, { direct: true }, &block)
58
+ instance.within_node({ type: 'send', message: 'create' }, { stop_at_first_match: true }, &block)
58
59
  end
59
60
 
60
61
  it 'parses goto_node' do
@@ -266,6 +267,55 @@ module Synvert::Core
266
267
  end
267
268
  end
268
269
 
270
+ describe '#test' do
271
+ let(:rewriter) { Rewriter.new('foo', 'bar') }
272
+
273
+ it 'writes new code to file' do
274
+ instance =
275
+ Rewriter::Instance.new rewriter, ['spec/**/*_spec.rb'] do
276
+ with_node type: 'send', receiver: 'FactoryGirl', message: 'create' do
277
+ replace_with 'create {{arguments}}'
278
+ end
279
+ end
280
+ input = <<~EOS
281
+ it 'uses factory_girl' do
282
+ user = FactoryGirl.create :user
283
+ post = FactoryGirl.create :post, user: user
284
+ assert post.valid?
285
+ end
286
+ EOS
287
+ expect(Dir).to receive(:glob).with('./spec/**/*_spec.rb').and_return(['spec/models/post_spec.rb'])
288
+ expect(File).to receive(:read).with('spec/models/post_spec.rb', encoding: 'UTF-8').and_return(input)
289
+ results = instance.test
290
+ expect(results[0].file_path).to eq 'spec/models/post_spec.rb'
291
+ expect(results[0].actions).to eq [
292
+ OpenStruct.new(start: 35, end: 59, new_code: 'create :user'),
293
+ OpenStruct.new(start: 69, end: 105, new_code: 'create :post, user: user')
294
+ ]
295
+ end
296
+
297
+ it 'does not write if file content is not changed' do
298
+ instance =
299
+ Rewriter::Instance.new rewriter, ['spec/spec_helper.rb'] do
300
+ with_node type: 'block', caller: { receiver: 'RSpec', message: 'configure' } do
301
+ unless_exist_node type: 'send', message: 'include', arguments: ['FactoryGirl::Syntax::Methods'] do
302
+ insert '{{arguments.first}}.include FactoryGirl::Syntax::Methods'
303
+ end
304
+ end
305
+ end
306
+ input = <<~EOS
307
+ RSpec.configure do |config|
308
+ config.include FactoryGirl::Syntax::Methods
309
+ end
310
+ EOS
311
+ expect(Dir).to receive(:glob).with('./spec/spec_helper.rb').and_return(['spec/spec_helper.rb'])
312
+ expect(File).to receive(:read).with('spec/spec_helper.rb', encoding: 'UTF-8').and_return(input)
313
+ result = instance.test
314
+ expect(result[0].file_path).to eq 'spec/spec_helper.rb'
315
+ expect(result[0].actions).to eq []
316
+ end
317
+ end
318
+
269
319
  describe '#process_with_node' do
270
320
  it 'resets current_node' do
271
321
  node1 = double
@@ -55,18 +55,6 @@ module Synvert::Core
55
55
  expect(block_nodes.size).to eq 2
56
56
  end
57
57
 
58
- it 'raises ParseError' do
59
- scope = described_class.new(instance, 'synvert') {}
60
- expect {
61
- scope.process
62
- }.to raise_error(NodeQuery::Compiler::ParseError)
63
-
64
- scope = described_class.new(instance, '.block <caller.arguments> .hash') {}
65
- expect {
66
- scope.process
67
- }.to raise_error(NodeQuery::Compiler::ParseError)
68
- end
69
-
70
58
  it 'raises InvalidOperatorError' do
71
59
  scope = described_class.new(instance, '.send[receiver IN FactoryGirl]') {}
72
60
  expect {
@@ -9,9 +9,11 @@ module Synvert::Core
9
9
  Rewriter::Instance.new(rewriter, 'file pattern')
10
10
  }
11
11
  let(:source) { <<~EOS }
12
- describe Post do
13
- it 'gets post' do
14
- FactoryGirl.create :post
12
+ describe User do
13
+ describe 'create' do
14
+ it 'creates user' do
15
+ FactoryGirl.create :user
16
+ end
15
17
  end
16
18
  end
17
19
  EOS
@@ -39,7 +41,7 @@ module Synvert::Core
39
41
  type: 'send',
40
42
  receiver: 'FactoryGirl',
41
43
  message: 'create',
42
- arguments: [':post'] do
44
+ arguments: [':user'] do
43
45
  run = true
44
46
  type_in_scope = node.type
45
47
  end
@@ -52,27 +54,37 @@ module Synvert::Core
52
54
  it 'matches multiple block nodes' do
53
55
  block_nodes = []
54
56
  scope =
55
- Rewriter::WithinScope.new(instance, { type: 'block' }, { stop_when_match: false }) do
57
+ Rewriter::WithinScope.new(instance, { type: 'block' }) do
58
+ block_nodes << node
59
+ end
60
+ scope.process
61
+ expect(block_nodes.size).to eq 3
62
+ end
63
+
64
+ it 'matches only 2 block nodes if including_self is false' do
65
+ block_nodes = []
66
+ scope =
67
+ Rewriter::WithinScope.new(instance, { type: 'block' }, { including_self: false }) do
56
68
  block_nodes << node
57
69
  end
58
70
  scope.process
59
71
  expect(block_nodes.size).to eq 2
60
72
  end
61
73
 
62
- it 'matches only one block node if no recursive' do
74
+ it 'matches only one block node if recursive is false' do
63
75
  block_nodes = []
64
76
  scope =
65
- Rewriter::WithinScope.new(instance, { type: 'block' }, { stop_when_match: true }) do
77
+ Rewriter::WithinScope.new(instance, { type: 'block' }, { recursive: false }) do
66
78
  block_nodes << node
67
79
  end
68
80
  scope.process
69
81
  expect(block_nodes.size).to eq 1
70
82
  end
71
83
 
72
- it 'matches only one direct node' do
84
+ it 'matches only one block node if stop_at_first_match is true' do
73
85
  block_nodes = []
74
86
  scope =
75
- Rewriter::WithinScope.new(instance, { type: 'block' }, { direct: true }) do
87
+ Rewriter::WithinScope.new(instance, { type: 'block' }, { stop_at_first_match: true }) do
76
88
  block_nodes << node
77
89
  end
78
90
  scope.process
@@ -37,6 +37,46 @@ module Synvert::Core
37
37
  rewriter.process
38
38
  end
39
39
 
40
+ describe '#process' do
41
+ it 'rewrites the file' do
42
+ rewriter = Rewriter.new('group', 'name') do
43
+ within_files '**/*.rb' do
44
+ with_node node_type: 'class', name: 'Foobar' do
45
+ replace :name, with: 'Synvert'
46
+ end
47
+ end
48
+ end
49
+ input = "class Foobar\nend"
50
+ output = "class Synvert\nend"
51
+ FakeFS do
52
+ File.write("code.rb", input)
53
+ rewriter.process
54
+ expect(File.read("code.rb")).to eq output
55
+ end
56
+ end
57
+ end
58
+
59
+ describe '#test' do
60
+ it 'gets test results' do
61
+ rewriter = Rewriter.new('group', 'name') do
62
+ within_files '**/*.rb' do
63
+ with_node node_type: 'class', name: 'Foobar' do
64
+ replace :name, with: 'Synvert'
65
+ end
66
+ end
67
+ end
68
+ input = "class Foobar\nend"
69
+ FakeFS do
70
+ File.write("code.rb", input)
71
+ results = rewriter.test
72
+ expect(results[0].file_path).to eq '/code.rb'
73
+ expect(results[0].affected?).to be_truthy
74
+ expect(results[0].conflicted?).to be_falsey
75
+ expect(results[0].actions).to eq [OpenStruct.new(start: 6, end: 12, new_code: 'Synvert')]
76
+ end
77
+ end
78
+ end
79
+
40
80
  describe 'parses within_file' do
41
81
  it 'does nothing if if_ruby does not match' do
42
82
  expect(File).to receive(:exist?).with('./.ruby-version').and_return(true)
@@ -245,7 +285,7 @@ module Synvert::Core
245
285
  it 'registers and calls rewriter in sandbox mode' do
246
286
  rewriter = Rewriter.new 'group', 'rewriter'
247
287
  expect(rewriter).to receive(:process_with_sandbox)
248
- Rewriter.call 'group', 'rewriter', true
288
+ Rewriter.call 'group', 'rewriter', run_instance: false
249
289
  end
250
290
 
251
291
  it 'raises RewriterNotFound if rewriter not found' do
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_runtime_dependency "activesupport"
22
+ spec.add_runtime_dependency "activesupport", "< 7.0.0"
23
23
  spec.add_runtime_dependency "erubis"
24
24
  spec.add_runtime_dependency "node_query"
25
25
  spec.add_runtime_dependency "node_mutation"
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: synvert-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Huang
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-02 00:00:00.000000000 Z
11
+ date: 2022-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "<"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 7.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "<"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: 7.0.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: erubis
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -106,6 +106,7 @@ files:
106
106
  - ".rspec"
107
107
  - CHANGELOG.md
108
108
  - Gemfile
109
+ - Gemfile.lock
109
110
  - Guardfile
110
111
  - LICENSE.txt
111
112
  - README.md