synvert-core 0.63.1 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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