synvert-core 1.6.0 → 1.8.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: 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