synvert-core 1.6.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: 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