synvert-core 0.63.1 → 1.0.1

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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +1 -1
  3. data/.gitignore +4 -0
  4. data/CHANGELOG.md +9 -1
  5. data/Guardfile +11 -2
  6. data/README.md +74 -34
  7. data/Rakefile +15 -1
  8. data/lib/synvert/core/array_ext.rb +41 -0
  9. data/lib/synvert/core/configuration.rb +12 -0
  10. data/lib/synvert/core/engine/erb.rb +9 -8
  11. data/lib/synvert/core/exceptions.rb +0 -4
  12. data/lib/synvert/core/node_ext.rb +232 -128
  13. data/lib/synvert/core/node_query/compiler/array.rb +34 -0
  14. data/lib/synvert/core/node_query/compiler/attribute.rb +51 -0
  15. data/lib/synvert/core/node_query/compiler/attribute_list.rb +24 -0
  16. data/lib/synvert/core/node_query/compiler/boolean.rb +23 -0
  17. data/lib/synvert/core/node_query/compiler/comparable.rb +79 -0
  18. data/lib/synvert/core/node_query/compiler/dynamic_attribute.rb +51 -0
  19. data/lib/synvert/core/node_query/compiler/expression.rb +88 -0
  20. data/lib/synvert/core/node_query/compiler/float.rb +23 -0
  21. data/lib/synvert/core/node_query/compiler/identifier.rb +41 -0
  22. data/lib/synvert/core/node_query/compiler/integer.rb +23 -0
  23. data/lib/synvert/core/node_query/compiler/invalid_operator_error.rb +7 -0
  24. data/lib/synvert/core/node_query/compiler/nil.rb +23 -0
  25. data/lib/synvert/core/node_query/compiler/parse_error.rb +7 -0
  26. data/lib/synvert/core/node_query/compiler/regexp.rb +37 -0
  27. data/lib/synvert/core/node_query/compiler/selector.rb +51 -0
  28. data/lib/synvert/core/node_query/compiler/string.rb +34 -0
  29. data/lib/synvert/core/node_query/compiler/symbol.rb +23 -0
  30. data/lib/synvert/core/node_query/compiler.rb +24 -0
  31. data/lib/synvert/core/node_query/lexer.rex +96 -0
  32. data/lib/synvert/core/node_query/lexer.rex.rb +293 -0
  33. data/lib/synvert/core/node_query/parser.racc.rb +518 -0
  34. data/lib/synvert/core/node_query/parser.y +84 -0
  35. data/lib/synvert/core/node_query.rb +36 -0
  36. data/lib/synvert/core/rewriter/action/append_action.rb +4 -3
  37. data/lib/synvert/core/rewriter/action/delete_action.rb +17 -8
  38. data/lib/synvert/core/rewriter/action/insert_action.rb +16 -7
  39. data/lib/synvert/core/rewriter/action/insert_after_action.rb +3 -2
  40. data/lib/synvert/core/rewriter/action/prepend_action.rb +3 -2
  41. data/lib/synvert/core/rewriter/action/remove_action.rb +16 -10
  42. data/lib/synvert/core/rewriter/action/replace_action.rb +15 -5
  43. data/lib/synvert/core/rewriter/action/replace_erb_stmt_with_expr_action.rb +18 -11
  44. data/lib/synvert/core/rewriter/action/replace_with_action.rb +6 -5
  45. data/lib/synvert/core/rewriter/action/wrap_action.rb +16 -7
  46. data/lib/synvert/core/rewriter/action.rb +22 -10
  47. data/lib/synvert/core/rewriter/any_value.rb +1 -0
  48. data/lib/synvert/core/rewriter/condition/if_exist_condition.rb +4 -0
  49. data/lib/synvert/core/rewriter/condition/if_only_exist_condition.rb +4 -0
  50. data/lib/synvert/core/rewriter/condition/unless_exist_condition.rb +4 -0
  51. data/lib/synvert/core/rewriter/condition.rb +11 -3
  52. data/lib/synvert/core/rewriter/gem_spec.rb +6 -3
  53. data/lib/synvert/core/rewriter/helper.rb +7 -4
  54. data/lib/synvert/core/rewriter/instance.rb +217 -104
  55. data/lib/synvert/core/rewriter/ruby_version.rb +4 -4
  56. data/lib/synvert/core/rewriter/scope/goto_scope.rb +5 -6
  57. data/lib/synvert/core/rewriter/scope/query_scope.rb +36 -0
  58. data/lib/synvert/core/rewriter/scope/within_scope.rb +10 -5
  59. data/lib/synvert/core/rewriter/scope.rb +8 -0
  60. data/lib/synvert/core/rewriter/warning.rb +1 -1
  61. data/lib/synvert/core/rewriter.rb +91 -43
  62. data/lib/synvert/core/version.rb +1 -1
  63. data/lib/synvert/core.rb +22 -6
  64. data/spec/synvert/core/engine/erb_spec.rb +2 -2
  65. data/spec/synvert/core/node_ext_spec.rb +36 -12
  66. data/spec/synvert/core/node_query/lexer_spec.rb +512 -0
  67. data/spec/synvert/core/node_query/parser_spec.rb +270 -0
  68. data/spec/synvert/core/rewriter/action_spec.rb +0 -4
  69. data/spec/synvert/core/rewriter/condition/if_only_exist_condition_spec.rb +1 -6
  70. data/spec/synvert/core/rewriter/gem_spec_spec.rb +1 -1
  71. data/spec/synvert/core/rewriter/helper_spec.rb +4 -1
  72. data/spec/synvert/core/rewriter/instance_spec.rb +31 -20
  73. data/spec/synvert/core/rewriter/scope/query_scope_spec.rb +74 -0
  74. data/spec/synvert/core/rewriter/scope/within_scope_spec.rb +12 -9
  75. data/spec/synvert/core/rewriter_spec.rb +4 -2
  76. data/synvert-core-ruby.gemspec +7 -2
  77. metadata +91 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5739f84132a6ece2551028571b7e6cb458812524f51ea11de6175eb621592d21
4
- data.tar.gz: a55b00a9431cacbe740405294b351cd8c5a1face1f2c20ba3a61897316dc7bd4
3
+ metadata.gz: f889cb977b7bf6e847bdeeac011ee0d23210ae8db7a0652e8c00d5d98c72a701
4
+ data.tar.gz: 46d220401cc8c9c0422428c2915862148b4ad8dc236f637ed52dbc45207de742
5
5
  SHA512:
6
- metadata.gz: ba153f740ea4d281dde99359dcf864beb49bdac33db3833878eebb9ba020966bb8957426936e65f7fc00ce84b76f7bb79fe2c01a5984ea155c5a4f6845c6e5b7
7
- data.tar.gz: 6ff6c82355e35e6f86a42f01bcbd57b4e25d6c0ccef0079b89d4980bca26a983bf187526f9f5af150ee7a970d906b3cb22dd69b1af9f84263d3d8231d4673df7
6
+ metadata.gz: 926df3dd8aa3689938e90c530fb432e0703cff2f89f35f46950b3630eb98b8da6de0e0d5eb75c5c24262f3712400d4570ac05abd5ebbbe6c6954d6c997048432
7
+ data.tar.gz: 321fb626763ca31e6156c462bb6846ce73d00639a620b0b7048b024fbe50d14f63f6d3209e3a534b0475c29c29e74dcf0a7c204f14ad0927244bd5cbb157b3d6
@@ -19,7 +19,7 @@ jobs:
19
19
  runs-on: ubuntu-latest
20
20
  strategy:
21
21
  matrix:
22
- ruby-version: ['2.5', '2.6', '2.7', '3.0', '3.1']
22
+ ruby-version: ['2.6', '2.7', '3.0', '3.1']
23
23
 
24
24
  steps:
25
25
  - uses: actions/checkout@v2
data/.gitignore CHANGED
@@ -15,3 +15,7 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+
19
+ lib/synvert/core/node_query/lexer.rex.rb
20
+ lib/synvert/core/node_query/parser.racc.rb
21
+ lib/synvert/core/node_query/parser.output
data/CHANGELOG.md CHANGED
@@ -1,8 +1,16 @@
1
1
  # CHANGELOG
2
2
 
3
- ## 0.63.1 (2022-02-26)
3
+ ## 1.0.0 (2022-04-25)
4
+
5
+ * Introduce new node query language
6
+ * Drop ruby 2.5 support
7
+
8
+ ## 0.64.0 (2022-04-02)
4
9
 
5
10
  * Read absolute path of Gemfile.lock
11
+ * Remove unused `Node#to_s`
12
+ * Yardoc comments
13
+ * Drop `within_direct_node(rules)`, use `within_node(rules, { direct: true })` instead
6
14
 
7
15
  ## 0.63.0 (2022-02-26)
8
16
 
data/Guardfile CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  guard :rspec, cmd: 'bundle exec rspec' do
4
4
  watch(%r{^spec/.+_spec\.rb$})
5
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
6
- watch('spec/spec_helper.rb') { "spec" }
5
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
6
+ watch('lib/synvert/core/node_query/lexer.rex.rb') { 'spec/synvert/core/node_query/lexer_spec.rb' }
7
+ watch('lib/synvert/core/node_query/compiler.rb') { 'spec/synvert/core/node_query/parser_spec.rb' }
8
+ watch(%r{^lib/synvert/core/node_query/compiler/.*\.rb$}) { 'spec/synvert/core/node_query/parser_spec.rb' }
9
+ watch('lib/synvert/core/node_query/parser.racc.rb') { 'spec/synvert/core/node_query/parser_spec.rb' }
10
+ watch('spec/spec_helper.rb') { "spec" }
11
+ end
12
+
13
+ guard :rake, task: 'generate' do
14
+ watch('lib/synvert/core/node_query/lexer.rex')
15
+ watch('lib/synvert/core/node_query/parser.y')
7
16
  end
data/README.md CHANGED
@@ -2,39 +2,79 @@
2
2
 
3
3
  <img src="https://synvert.xinminlabs.com/img/logo_96.png" alt="logo" width="32" height="32" />
4
4
 
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)
5
6
  ![Main workflow](https://github.com/xinminlabs/synvert-core-ruby/actions/workflows/main.yml/badge.svg)
6
- [![Gem Version](https://badge.fury.io/rb/synvert-core.png)](http://badge.fury.io/rb/synvert-core)
7
7
 
8
- synvert-core-ruby provides a dsl to convert ruby source code.
9
-
10
- ## Installation
11
-
12
- Add this line to your application's Gemfile:
13
-
14
- gem 'synvert-core'
15
-
16
- And then execute:
17
-
18
- $ bundle
19
-
20
- Or install it yourself as:
21
-
22
- $ gem install synvert-core
23
-
24
-
25
- ## Documentation
26
-
27
- [Website][1]
28
-
29
- [RDoc][2]
30
-
31
- ## Contributing
32
-
33
- 1. Fork it ( https://github.com/[my-github-username]/synvert-core-ruby/fork )
34
- 2. Create your feature branch (`git checkout -b my-new-feature`)
35
- 3. Commit your changes (`git commit -am 'Add some feature'`)
36
- 4. Push to the branch (`git push origin my-new-feature`)
37
- 5. Create a new Pull Request
38
-
39
- [1]: https://synvert.xinminlabs.com
40
- [2]: https://rubydoc.info/github/xinminlabs/synvert-core-ruby/master/frames
8
+ Synvert core provides a set of DSLs to rewrite ruby code. e.g.
9
+
10
+ ```ruby
11
+ Synvert::Rewriter.new 'ruby', 'map_and_flatten_to_flat_map' do
12
+ description <<~EOS
13
+ It converts `map` and `flatten` to `flat_map`
14
+
15
+ ```ruby
16
+ enum.map do
17
+ # do something
18
+ end.flatten
19
+ ```
20
+
21
+ =>
22
+
23
+ ```ruby
24
+ enum.flat_map do
25
+ # do something
26
+ end
27
+ ```
28
+ EOS
29
+
30
+ within_files Synvert::ALL_RUBY_FILES do
31
+ with_node type: 'send', receiver: { type: 'block', caller: { type: 'send', message: 'map' } }, message: 'flatten', arguments: { size: 0 } do
32
+ delete :message, :dot
33
+ replace 'receiver.caller.message', with: 'flat_map'
34
+ end
35
+ end
36
+ end
37
+ ```
38
+
39
+ Want to see more examples, check out [synvert-snippets-ruby](https://github.com/xinminlabs/synvert-snippets-ruby).
40
+
41
+ Want to use the CLI, check out [synvert-ruby](https://github.com/xinminlabs/synvert-ruby).
42
+
43
+ DSLs are as follows
44
+
45
+ * [description](./Synvert/Core/Rewriter.html#description-instance_method) - set description of the rewriter
46
+ * [if_ruby](./Synvert/Core/Rewriter.html#if_ruby-instance_method) - check if ruby version is greater than or equal to the specified ruby version
47
+ * [if_gem](./Synvert/Core/Rewriter.html#if_gem-instance_method) - compare version of specified gem
48
+ * [within_files](./Synvert/Core/Rewriter.html#within_files-instance_method) - find specified files
49
+ * [within_file](./Synvert/Core/Rewriter.html#within_file-instance_method) - alias to within_files
50
+ * [add_file](./Synvert/Core/Rewriter.html#add_file-instance_method) - add a new file
51
+ * [remove_file](./Synvert/Core/Rewriter.html#remove_file-instance_method) - remove a file
52
+ * [helper_method](./Synvert/Core/Rewriter.html#helper_method-instance_method) - define a helper method
53
+ * [add_snippet](./Synvert/Core/Rewriter.html#add_snippet-instance_method) - call another rewriter
54
+ * [todo](./Synvert/Core/Rewriter.html#todo-instance_method) - set todo
55
+ * [redo_until_no_change](./Synvert/Core/Rewriter.html#redo_until_no_change-instance_method) - run the snippet until no change
56
+
57
+ Scopes:
58
+
59
+ * [within_node](./Synvert/Core/Rewriter/Instance.html#within_node-instance_method) - recursively find matching ast nodes
60
+ * [with_node](./Synvert/Core/Rewriter/Instance.html#with_node-instance_method) - alias to within_node
61
+ * [goto_node](./Synvert/Core/Rewriter/Instance.html#goto_node-instance_method) - go to a child node
62
+
63
+ Conditions:
64
+
65
+ * [if_exist_node](./Synvert/Core/Rewriter/Instance.html#if_exist_node-instance_method) - check if matching node exist in the child nodes
66
+ * [unless_exist_node](./Synvert/Core/Rewriter/Instance.html#unless_exist_node-instance_method) - check if matching node doesn't exist in the child nodes
67
+ * [if_only_exist_node](./Synvert/Core/Rewriter/Instance.html#if_only_exist_node-instance_method) - check if current node has only one child node and the child node matches rules
68
+
69
+ Actions:
70
+
71
+ * [append](./Synvert/Core/Rewriter/Instance.html#append-instance_method) - append the code to the bottom of current node body
72
+ * [prepend](./Synvert/Core/Rewriter/Instance.html#prepend-instance_method) - prepend the code to the bottom of current node body
73
+ * [insert](./Synvert/Core/Rewriter/Instance.html#insert-instance_method) - insert code
74
+ * [insert_after](./Synvert/Core/Rewriter/Instance.html#insert_after-instance_method) - insert the code next to the current node
75
+ * [replace](./Synvert/Core/Rewriter/Instance.html#replace-instance_method) - replace the code of specified child nodes
76
+ * [delete](./Synvert/Core/Rewriter/Instance.html#delete-instance_method) - delete the code specified child nodes
77
+ * [wrap](./Synvert/Core/Rewriter/Instance.html#wrap-instance_method) - wrap the current node with code
78
+ * [replace_with](./Synvert/Core/Rewriter/Instance.html#replace_with-instance_method) - replace the whole code of current node
79
+ * [warn](./Synvert/Core/Rewriter/Instance.html#warn-instance_method) - warn message
80
+ * [replace_erb_stmt_with_expr](./Synvert/Core/Rewriter/Instance.html#replace_erb_stmt_with_expr-instance_method) - replace erb stmt code to expr code
data/Rakefile CHANGED
@@ -2,7 +2,21 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require 'rspec/core/rake_task'
5
-
6
5
  RSpec::Core::RakeTask.new(:spec)
6
+ require 'oedipus_lex'
7
+ Rake.application.rake_require "oedipus_lex"
8
+
9
+ file "lib/synvert/core/node_query/lexer.rex.rb" => "lib/synvert/core/node_query/lexer.rex"
10
+ file "lib/synvert/core/node_query/parser.racc.rb" => "lib/synvert/core/node_query/parser.y"
11
+
12
+ task :lexer => "lib/synvert/core/node_query/lexer.rex.rb"
13
+ task :parser => "lib/synvert/core/node_query/parser.racc.rb"
14
+ task :generate => [:lexer, :parser]
15
+
16
+ rule '.racc.rb' => '.y' do |t|
17
+ cmd = "bundle exec racc -l -v -o #{t.name} #{t.source}"
18
+ sh cmd
19
+ end
7
20
 
8
21
  task :default => :spec
22
+ task :spec => :generate
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Extend Array.
4
+ class Array
5
+ # Get child node by the name.
6
+ #
7
+ # @param child_name [String] name of child node.
8
+ # @return [Parser::AST::Node] the child node.
9
+ def child_node_by_name(child_name)
10
+ direct_child_name, nested_child_name = child_name.split('.', 2)
11
+ child_direct_child_node = direct_child_name =~ /\A\d+\z/ ? self[direct_child_name.to_i - 1] : self.send(direct_child_name)
12
+ return child_direct_child_node.child_node_by_name(nested_child_name) if nested_child_name
13
+ return child_direct_child_node if child_direct_child_node
14
+
15
+ raise Synvert::Core::MethodNotSupported,
16
+ "child_node_by_name is not handled for #{map(&:debug_info).join("\n")}, child_name: #{child_name}"
17
+ end
18
+
19
+ # Get the source range of child node.
20
+ #
21
+ # @param child_name [String] name of child node.
22
+ # @return [Parser::Source::Range] source range of child node.
23
+ def child_node_range(child_name)
24
+ direct_child_name, nested_child_name = child_name.split('.', 2)
25
+ child_direct_child_node = direct_child_name =~ /\A\d+\z/ ? self[direct_child_name.to_i - 1] : self.send(direct_child_name)
26
+ if nested_child_name
27
+ return child_direct_child_node.child_node_range(nested_child_name)
28
+ elsif child_direct_child_node
29
+ return (
30
+ Parser::Source::Range.new(
31
+ '(string)',
32
+ child_direct_child_node.loc.expression.begin_pos,
33
+ child_direct_child_node.loc.expression.end_pos
34
+ )
35
+ )
36
+ else
37
+ raise Synvert::Core::MethodNotSupported,
38
+ "child_node_range is not handled for #{map(&:debug_info).join("\n")}, child_name: #{child_name}"
39
+ end
40
+ end
41
+ end
@@ -4,16 +4,28 @@ 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
9
+ # @!attribute [w] show_run_process
7
10
  attr_writer :path, :skip_files, :show_run_process
8
11
 
12
+ # Get the path.
13
+ #
14
+ # @return [String] default is '.'
9
15
  def path
10
16
  @path || '.'
11
17
  end
12
18
 
19
+ # Get a list of skip files.
20
+ #
21
+ # @return [Array<String>] default is [].
13
22
  def skip_files
14
23
  @skip_files || []
15
24
  end
16
25
 
26
+ # Check if show run process.
27
+ #
28
+ # @return [Boolean] default is false
17
29
  def show_run_process
18
30
  @show_run_process || false
19
31
  end
@@ -43,11 +43,13 @@ module Synvert::Core
43
43
  end
44
44
 
45
45
  def decode_html_output(source)
46
- source.gsub(/@output_buffer.safe_append='(.+?)'.freeze;/m) { reverse_escape_text(Regexp.last_match(1)) }.gsub(
47
- /@output_buffer.safe_append=\((.+?)\);#{ERUBY_EXPR_SPLITTER}/mo
48
- ) { reverse_escape_text(Regexp.last_match(1)) }.gsub(
49
- /@output_buffer.safe_append=(.+?)\s+(do|\{)(\s*\|[^|]*\|)?\s*#{ERUBY_EXPR_SPLITTER}/mo
50
- ) { reverse_escape_text(Regexp.last_match(1)) }
46
+ source.gsub(/@output_buffer.safe_append='(.+?)'.freeze;/m) { reverse_escape_text(Regexp.last_match(1)) }
47
+ .gsub(
48
+ /@output_buffer.safe_append=\((.+?)\);#{ERUBY_EXPR_SPLITTER}/mo
49
+ ) { reverse_escape_text(Regexp.last_match(1)) }
50
+ .gsub(
51
+ /@output_buffer.safe_append=(.+?)\s+(do|\{)(\s*\|[^|]*\|)?\s*#{ERUBY_EXPR_SPLITTER}/mo
52
+ ) { reverse_escape_text(Regexp.last_match(1)) }
51
53
  end
52
54
 
53
55
  def remove_erubis_buf(source)
@@ -64,6 +66,7 @@ module Synvert::Core
64
66
 
65
67
  # borrowed from rails
66
68
  class Erubis < ::Erubis::Eruby
69
+ BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
67
70
  def add_preamble(src)
68
71
  @newline_pending = 0
69
72
  src << '@output_buffer = output_buffer || ActionView::OutputBuffer.new;'
@@ -76,7 +79,7 @@ module Synvert::Core
76
79
  @newline_pending += 1
77
80
  else
78
81
  src << "@output_buffer.safe_append='"
79
- src << "\n" * @newline_pending if @newline_pending > 0
82
+ src << ("\n" * @newline_pending) if @newline_pending > 0
80
83
  src << escape_text(text)
81
84
  src << "'.freeze;"
82
85
 
@@ -95,8 +98,6 @@ module Synvert::Core
95
98
  end
96
99
  end
97
100
 
98
- BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
99
-
100
101
  def add_expr_literal(src, code)
101
102
  flush_newline_if_pending(src)
102
103
  if BLOCK_EXPR.match?(code)
@@ -5,10 +5,6 @@ module Synvert::Core
5
5
  class RewriterNotFound < RuntimeError
6
6
  end
7
7
 
8
- # Gemfile.lock not found exception.
9
- class GemfileLockNotFound < RuntimeError
10
- end
11
-
12
8
  # Method not supported exception.
13
9
  class MethodNotSupported < RuntimeError
14
10
  end