synvert-core 1.6.0 → 1.8.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: 00661750a3522a9983d5eec1f064f62bf0d335e05e3b2a9cb7bb7222b04e20a2
4
+ data.tar.gz: 5a35b831ca678486f4a51db2ae8a57bed58617299a0c26c9cbc0c462351ffd3d
5
5
  SHA512:
6
- metadata.gz: 4b31e94f8071af04f1ae7b1292bb5af91e52ba047949535667cb6765a5ef32c9bbcf855dd92a212aa640b0185f42fbd1bbcf4614f424c596f9c52f62a313a643
7
- data.tar.gz: 197318ccffd17c9eb60251731bf49f45198e2b5fb5812c22d4fe6d0d07bac94bff638bc5a125d05be2ccec930b16300ccdb83ffdd53bf8cf0c4cd8ad22a34c94
6
+ metadata.gz: b18bcf0f9f344c2c8661d7e4edf1a4f768d67175f45b34d2bb37b321f38681840c29b8e9a0fa61ee295329909f3093de4b7703e6dec21066190336ee8ceb1b5a
7
+ data.tar.gz: 0f334dc34d82d15235381f3a9be7651707b5a7a0e5614e29f48310407c70384ac6f1594f2b8643dc97577e0e55a4b6165f3174071e2887f424a76ed4d5f3b3cb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.8.0 (2022-09-17)
4
+
5
+ * Rename config `path` to `root_path`
6
+ * Rename config `skip_files` to `skip_paths`
7
+ * Add config `only_paths`
8
+ * Change dir to `root_path`
9
+
10
+ ## 1.7.0 (2022-09-16)
11
+
12
+ * Add `Rewriter#test`
13
+ * Use option `run_instance` instead of `sandbox`
14
+
3
15
  ## 1.6.0 (2022-09-15)
4
16
 
5
17
  * 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.8.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
@@ -4,23 +4,31 @@ module Synvert::Core
4
4
  # Synvert global configuration.
5
5
  class Configuration
6
6
  class << self
7
- # @!attribute [w] path
8
- # @!attribute [w] skip_files
7
+ # @!attribute [w] root_path
8
+ # @!attribute [w] skip_paths
9
+ # @!attribute [w] only_paths
9
10
  # @!attribute [w] show_run_process
10
- attr_writer :path, :skip_files, :show_run_process
11
+ attr_writer :root_path, :skip_paths, :only_paths, :show_run_process
11
12
 
12
13
  # Get the path.
13
14
  #
14
15
  # @return [String] default is '.'
15
- def path
16
- @path || '.'
16
+ def root_path
17
+ @root_path || '.'
17
18
  end
18
19
 
19
- # Get a list of skip files.
20
+ # Get a list of skip paths.
20
21
  #
21
22
  # @return [Array<String>] default is [].
22
- def skip_files
23
- @skip_files || []
23
+ def skip_paths
24
+ @skip_paths || []
25
+ end
26
+
27
+ # Get a list of only paths.
28
+ #
29
+ # @return [Array<String>] default is [].
30
+ def only_paths
31
+ @only_paths || []
24
32
  end
25
33
 
26
34
  # Check if show run process.
@@ -22,12 +22,12 @@ module Synvert::Core
22
22
  #
23
23
  # @return [Boolean] true if matches, otherwise false.
24
24
  def match?
25
- gemfile_lock_path = File.expand_path(File.join(Configuration.path, 'Gemfile.lock'))
25
+ gemfile_lock_path = File.expand_path(File.join(Configuration.root_path, 'Gemfile.lock'))
26
26
 
27
27
  # if Gemfile.lock does not exist, just ignore this check
28
28
  return true unless File.exist?(gemfile_lock_path)
29
29
 
30
- ENV['BUNDLE_GEMFILE'] = Configuration.path # make sure bundler reads Gemfile.lock in the correct path
30
+ ENV['BUNDLE_GEMFILE'] = Configuration.root_path # make sure bundler reads Gemfile.lock in the correct path
31
31
  parser = Bundler::LockfileParser.new(File.read(gemfile_lock_path))
32
32
  parser.specs.any? { |spec| Gem::Dependency.new(@name, @version).match?(spec) }
33
33
  end
@@ -30,14 +30,19 @@ 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
- @file_patterns.each do |file_pattern|
36
- Dir.glob(File.join(Configuration.path, file_pattern)).each do |file_path|
37
- next if Configuration.skip_files.include?(file_path)
35
+ get_file_paths.each do |file_path|
36
+ process_file(file_path)
37
+ end
38
+ end
38
39
 
39
- process_file(file_path)
40
- end
40
+ # Test the instance.
41
+ # It finds specified files, for each file, it executes the block code, tests the original code,
42
+ # then returns the actions.
43
+ def test
44
+ get_file_paths.each do |file_path|
45
+ test_file(file_path)
41
46
  end
42
47
  end
43
48
 
@@ -378,6 +383,67 @@ module Synvert::Core
378
383
  end
379
384
  end
380
385
 
386
+ # Test one file.
387
+ #
388
+ # @param file_path [String]
389
+ def test_file(file_path)
390
+ @current_file = file_path
391
+ source = read_source(file_path)
392
+ @current_mutation = NodeMutation.new(source)
393
+ begin
394
+ node = parse_code(file_path, source)
395
+
396
+ process_with_node(node) do
397
+ instance_eval(&@block)
398
+ rescue NoMethodError => e
399
+ puts [
400
+ "error: #{e.message}",
401
+ "file: #{file_path}",
402
+ "source: #{source}",
403
+ "line: #{current_node.line}"
404
+ ].join("\n")
405
+ raise
406
+ end
407
+
408
+ result = @current_mutation.test
409
+ result.file_path = file_path
410
+ result
411
+ rescue Parser::SyntaxError
412
+ puts "[Warn] file #{file_path} was not parsed correctly."
413
+ # do nothing, iterate next file
414
+ end
415
+ end
416
+
417
+ # Get file paths.
418
+ # @return [Array<String>] file paths
419
+ def get_file_paths
420
+ Dir.chdir(Configuration.root_path) do
421
+ only_paths = Configuration.only_paths.size > 0 ? Configuration.only_paths : ["."]
422
+ only_paths.flat_map do |only_path|
423
+ @file_patterns.flat_map do |file_pattern|
424
+ pattern = only_path == "." ? file_pattern : File.join(only_path, file_pattern)
425
+ Dir.glob(pattern) - get_skip_files
426
+ end
427
+ end
428
+ end
429
+ end
430
+
431
+ # Get skip files.
432
+ # @return [Array<String>] skip files
433
+ def get_skip_files
434
+ @skip_files ||= Configuration.skip_paths.flat_map do |skip_path|
435
+ if File.directory?(skip_path)
436
+ Dir.glob(File.join(skip_path, "**/*"))
437
+ elsif File.file?(skip_path)
438
+ [skip_path]
439
+ elsif skip_path.end_with?("**") || skip_path.end_with?("**/")
440
+ Dir.glob(File.join(skip_path, "*"))
441
+ else
442
+ Dir.glob(skip_path)
443
+ end
444
+ end
445
+ end
446
+
381
447
  # Read file source.
382
448
  # @param file_path [String] file path
383
449
  # @return [String] file source
@@ -16,14 +16,14 @@ module Synvert::Core
16
16
  #
17
17
  # @return [Boolean] true if matches, otherwise false.
18
18
  def match?
19
- if File.exist?(File.join(Configuration.path, '.ruby-version'))
19
+ if File.exist?(File.join(Configuration.root_path, '.ruby-version'))
20
20
  version_file = '.ruby-version'
21
- elsif File.exist?(File.join(Configuration.path, '.rvmrc'))
21
+ elsif File.exist?(File.join(Configuration.root_path, '.rvmrc'))
22
22
  version_file = '.rvmrc'
23
23
  end
24
24
  return true unless version_file
25
25
 
26
- version = File.read(File.join(Configuration.path, version_file))
26
+ version = File.read(File.join(Configuration.root_path, version_file))
27
27
  Gem::Version.new(version) >= Gem::Version.new(@version)
28
28
  end
29
29
  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 = { run_instance: true })
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,9 +272,9 @@ 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
- filepath = File.join(Configuration.path, filename)
277
+ filepath = File.join(Configuration.root_path, filename)
254
278
  if File.exist?(filepath)
255
279
  puts "File #{filepath} already exists."
256
280
  return
@@ -267,9 +291,9 @@ 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
- file_path = File.join(Configuration.path, filename)
296
+ file_path = File.join(Configuration.root_path, filename)
273
297
  File.delete(file_path) if File.exist?(file_path)
274
298
  end
275
299
 
@@ -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.8.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.8.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-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport