synvert-core 1.6.0 → 1.7.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
  SHA256:
3
- metadata.gz: 5d838da042cfdbc68e4a235117a465ca340fa732203f7470684a9279e6797fff
4
- data.tar.gz: ad784c3e09faa73a55e0bf0ad51a982230d9a1b2a328c43414e4aa297e38a00e
3
+ metadata.gz: 4e0e8e77a7f85aa23300c28aaf44fca1d6b0a7ffad9177bc9925cfd7541f4e65
4
+ data.tar.gz: eb1b9267dbf8264a9c5978dd019dc0c26fcc6613cf0413b767c8fdf4c763cb00
5
5
  SHA512:
6
- metadata.gz: 4b31e94f8071af04f1ae7b1292bb5af91e52ba047949535667cb6765a5ef32c9bbcf855dd92a212aa640b0185f42fbd1bbcf4614f424c596f9c52f62a313a643
7
- data.tar.gz: 197318ccffd17c9eb60251731bf49f45198e2b5fb5812c22d4fe6d0d07bac94bff638bc5a125d05be2ccec930b16300ccdb83ffdd53bf8cf0c4cd8ad22a34c94
6
+ metadata.gz: 26a85edf49b07f5afc0688152bf63bf65966e72f15d58da615b8e4b6575e28a74978113a9dbbed78f0cf076e0d0a5d54d486ef83af6a46a0ce7e1b69547b33c1
7
+ data.tar.gz: 21bb822480bd6ea79725d27eabb48c3306da71dec81fb817c8d94027b7457e5ae1ed3d78d9d5d870c103f173e2a3bc00e8b45273c66bbd4dfcb259e2ada254bc
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
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
+
3
8
  ## 1.6.0 (2022-09-15)
4
9
 
5
10
  * Make use of `NodeQuery` to query nodes
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 CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- synvert-core (1.6.0)
4
+ synvert-core (1.7.0)
5
5
  activesupport (< 7.0.0)
6
6
  erubis
7
7
  node_mutation
@@ -23,6 +23,7 @@ GEM
23
23
  concurrent-ruby (1.1.10)
24
24
  diff-lcs (1.5.0)
25
25
  erubis (2.7.0)
26
+ fakefs (1.8.0)
26
27
  ffi (1.15.5)
27
28
  formatador (1.1.0)
28
29
  guard (2.18.0)
@@ -48,10 +49,10 @@ GEM
48
49
  method_source (1.0.0)
49
50
  minitest (5.16.3)
50
51
  nenv (0.3.0)
51
- node_mutation (1.2.1)
52
- activesupport
52
+ node_mutation (1.3.3)
53
+ activesupport (< 7.0.0)
53
54
  erubis
54
- node_query (1.5.0)
55
+ node_query (1.6.0)
55
56
  activesupport (< 7.0.0)
56
57
  notiffany (0.1.3)
57
58
  nenv (~> 0.1)
@@ -90,6 +91,7 @@ PLATFORMS
90
91
  ruby
91
92
 
92
93
  DEPENDENCIES
94
+ fakefs
93
95
  guard
94
96
  guard-rspec
95
97
  rake
@@ -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]
@@ -378,6 +393,37 @@ module Synvert::Core
378
393
  end
379
394
  end
380
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
+
381
427
  # Read file source.
382
428
  # @param file_path [String] file path
383
429
  # @return [String] file source
@@ -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.6.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
@@ -267,6 +267,55 @@ module Synvert::Core
267
267
  end
268
268
  end
269
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
+
270
319
  describe '#process_with_node' do
271
320
  it 'resets current_node' do
272
321
  node1 = double
@@ -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
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: synvert-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.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-09-15 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